From 4e69a9b329500aed23cd620886f4980be7128426 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 15:59:15 +0100 Subject: [PATCH] fix(qa): restore safe no-fork gateway runtime --- extensions/qa-lab/src/gateway-child.test.ts | 2 +- extensions/qa-lab/src/gateway-child.ts | 18 ++---- .../qa-lab/src/gateway-rpc-client.test.ts | 13 +--- extensions/qa-lab/src/gateway-rpc-client.ts | 61 +++++-------------- 4 files changed, 23 insertions(+), 71 deletions(-) diff --git a/extensions/qa-lab/src/gateway-child.test.ts b/extensions/qa-lab/src/gateway-child.test.ts index f57bfdeb7a5..05260727d10 100644 --- a/extensions/qa-lab/src/gateway-child.test.ts +++ b/extensions/qa-lab/src/gateway-child.test.ts @@ -26,7 +26,7 @@ function createParams(baseEnv?: NodeJS.ProcessEnv) { } describe("buildQaRuntimeEnv", () => { - it("allows normal reply config flows while keeping fast test mode", () => { + it("keeps the slow-reply QA opt-out enabled under fast mode", () => { const env = buildQaRuntimeEnv({ ...createParams(), providerMode: "mock-openai", diff --git a/extensions/qa-lab/src/gateway-child.ts b/extensions/qa-lab/src/gateway-child.ts index 63eccc0892b..4f6a598413d 100644 --- a/extensions/qa-lab/src/gateway-child.ts +++ b/extensions/qa-lab/src/gateway-child.ts @@ -110,8 +110,7 @@ export function buildQaRuntimeEnv(params: { OPENCLAW_SKIP_CANVAS_HOST: "1", OPENCLAW_NO_RESPAWN: "1", OPENCLAW_TEST_FAST: "1", - // QA uses the fast runtime envelope for speed, but it still exercises - // normal config-driven heartbeats and runtime config writes. + // QA still exercises normal reply-config flows under the fast envelope. OPENCLAW_ALLOW_SLOW_REPLY_TESTS: "1", XDG_CONFIG_HOME: params.xdgConfigHome, XDG_DATA_HOME: params.xdgDataHome, @@ -120,10 +119,6 @@ export function buildQaRuntimeEnv(params: { return normalizeQaProviderModeEnv(env, params.providerMode); } -export const __testing = { - buildQaRuntimeEnv, -}; - async function waitForGatewayReady(params: { baseUrl: string; logs: () => string; @@ -140,17 +135,17 @@ async function waitForGatewayReady(params: { `gateway exited before becoming healthy (exitCode=${String(params.child.exitCode)}, signal=${String(params.child.signalCode)}):\n${params.logs()}`, ); } - try { - for (const readyPath of ["/readyz", "/healthz"]) { - const response = await fetch(`${params.baseUrl}${readyPath}`, { + for (const healthPath of ["/readyz", "/healthz"]) { + try { + const response = await fetch(`${params.baseUrl}${healthPath}`, { signal: AbortSignal.timeout(2_000), }); if (response.ok) { return; } + } catch { + // retry until timeout } - } catch { - // retry until timeout } await sleep(250); } @@ -270,7 +265,6 @@ export async function startQaGatewayChild(params: { rpcClient = await startQaGatewayRpcClient({ wsUrl, token: gatewayToken, - env, logs, }); } catch (error) { diff --git a/extensions/qa-lab/src/gateway-rpc-client.test.ts b/extensions/qa-lab/src/gateway-rpc-client.test.ts index 52b4f9b1826..2f13d6f53e1 100644 --- a/extensions/qa-lab/src/gateway-rpc-client.test.ts +++ b/extensions/qa-lab/src/gateway-rpc-client.test.ts @@ -21,24 +21,18 @@ describe("startQaGatewayRpcClient", () => { gatewayRpcMock.reset(); }); - it("calls the in-process gateway cli helper with the qa runtime env", async () => { + it("calls the in-process gateway cli helper without mutating process.env", async () => { const originalHome = process.env.OPENCLAW_HOME; delete process.env.OPENCLAW_HOME; - delete process.env.OPENCLAW_QA_TEST_ONLY; gatewayRpcMock.callGatewayFromCli.mockImplementationOnce(async () => { - expect(process.env.OPENCLAW_HOME).toBe("/tmp/openclaw-home"); - expect(process.env.OPENCLAW_QA_TEST_ONLY).toBe("1"); + expect(process.env.OPENCLAW_HOME).toBeUndefined(); return { ok: true }; }); const client = await startQaGatewayRpcClient({ wsUrl: "ws://127.0.0.1:18789", token: "qa-token", - env: { - OPENCLAW_HOME: "/tmp/openclaw-home", - OPENCLAW_QA_TEST_ONLY: "1", - } as NodeJS.ProcessEnv, logs: () => "qa logs", }); @@ -63,7 +57,6 @@ describe("startQaGatewayRpcClient", () => { ); expect(process.env.OPENCLAW_HOME).toBe(originalHome); - expect(process.env.OPENCLAW_QA_TEST_ONLY).toBeUndefined(); }); it("wraps request failures with gateway logs", async () => { @@ -71,7 +64,6 @@ describe("startQaGatewayRpcClient", () => { const client = await startQaGatewayRpcClient({ wsUrl: "ws://127.0.0.1:18789", token: "qa-token", - env: { OPENCLAW_HOME: "/tmp/openclaw-home" } as NodeJS.ProcessEnv, logs: () => "qa logs", }); @@ -84,7 +76,6 @@ describe("startQaGatewayRpcClient", () => { const client = await startQaGatewayRpcClient({ wsUrl: "ws://127.0.0.1:18789", token: "qa-token", - env: { OPENCLAW_HOME: "/tmp/openclaw-home" } as NodeJS.ProcessEnv, logs: () => "qa logs", }); diff --git a/extensions/qa-lab/src/gateway-rpc-client.ts b/extensions/qa-lab/src/gateway-rpc-client.ts index e33bfb18b24..48c3fb6fe23 100644 --- a/extensions/qa-lab/src/gateway-rpc-client.ts +++ b/extensions/qa-lab/src/gateway-rpc-client.ts @@ -18,34 +18,6 @@ function formatQaGatewayRpcError(error: unknown, logs: () => string) { let qaGatewayRpcQueue = Promise.resolve(); -async function withScopedProcessEnv(env: NodeJS.ProcessEnv, task: () => Promise): Promise { - const original = new Map(); - const keys = new Set([...Object.keys(process.env), ...Object.keys(env)]); - - for (const key of keys) { - original.set(key, process.env[key]); - const nextValue = env[key]; - if (nextValue === undefined) { - delete process.env[key]; - continue; - } - process.env[key] = nextValue; - } - - try { - return await task(); - } finally { - for (const key of keys) { - const previousValue = original.get(key); - if (previousValue === undefined) { - delete process.env[key]; - continue; - } - process.env[key] = previousValue; - } - } -} - async function runQueuedQaGatewayRpc(task: () => Promise): Promise { const run = qaGatewayRpcQueue.then(task, task); qaGatewayRpcQueue = run.then( @@ -58,7 +30,6 @@ async function runQueuedQaGatewayRpc(task: () => Promise): Promise { export async function startQaGatewayRpcClient(params: { wsUrl: string; token: string; - env: NodeJS.ProcessEnv; logs: () => string; }): Promise { const wrapError = (error: unknown) => formatQaGatewayRpcError(error, params.logs); @@ -72,24 +43,20 @@ export async function startQaGatewayRpcClient(params: { try { return await runQueuedQaGatewayRpc( async () => - await withScopedProcessEnv( - params.env, - async () => - await callGatewayFromCli( - method, - { - url: params.wsUrl, - token: params.token, - timeout: String(opts?.timeoutMs ?? 20_000), - expectFinal: opts?.expectFinal, - json: true, - }, - rpcParams ?? {}, - { - expectFinal: opts?.expectFinal, - progress: false, - }, - ), + await callGatewayFromCli( + method, + { + url: params.wsUrl, + token: params.token, + timeout: String(opts?.timeoutMs ?? 20_000), + expectFinal: opts?.expectFinal, + json: true, + }, + rpcParams ?? {}, + { + expectFinal: opts?.expectFinal, + progress: false, + }, ), ); } catch (error) {