From 5a0fcc2ff92b680612a0df96cdc7337a1759d820 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 16 Jul 2026 13:59:34 -0700 Subject: [PATCH] test(cli): distinguish silent hook relay completion (#109339) --- src/cli/hooks-cli.process.test.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/cli/hooks-cli.process.test.ts b/src/cli/hooks-cli.process.test.ts index 3bacc5090f4d..6aafe9b18fe7 100644 --- a/src/cli/hooks-cli.process.test.ts +++ b/src/cli/hooks-cli.process.test.ts @@ -16,6 +16,7 @@ const tempDirs = useAutoCleanupTempDirTracker(afterEach); const activeChildren = new Set(); const outputTimeoutMs = 20_000; const exitAfterOutputTimeoutMs = 5_000; +const exitOnlyTimeoutMs = 45_000; afterEach(async () => { nativeHookRelayTesting.clearNativeHookRelaysForTests(); @@ -110,6 +111,7 @@ async function createLingeringPreloadFixture(): Promise<{ async function runHooksCli(params: { args: string[]; + completion: "exit" | "output-then-exit"; label: string; env?: NodeJS.ProcessEnv; stdin?: string; @@ -139,13 +141,16 @@ async function runHooksCli(params: { }>((resolve, reject) => { let timedOut = false; let outputObserved = false; + // Silent relay success has no stream milestone. Give it an exit deadline + // while keeping the tighter post-output deadline for leaked handles. + const initialTimeoutMs = params.completion === "exit" ? exitOnlyTimeoutMs : outputTimeoutMs; let timer = setTimeout(() => { timedOut = true; child.kill("SIGKILL"); - }, outputTimeoutMs); + }, initialTimeoutMs); child.stdout.on("data", (chunk: string) => { stdout += chunk; - if (outputObserved) { + if (params.completion === "exit" || outputObserved) { return; } outputObserved = true; @@ -167,9 +172,12 @@ async function runHooksCli(params: { clearTimeout(timer); activeChildren.delete(child); if (timedOut) { - const timeoutMessage = outputObserved - ? `${params.label} did not exit within ${exitAfterOutputTimeoutMs}ms after emitting output` - : `${params.label} did not emit output within ${outputTimeoutMs}ms`; + const timeoutMessage = + params.completion === "exit" + ? `${params.label} did not exit within ${exitOnlyTimeoutMs}ms` + : outputObserved + ? `${params.label} did not exit within ${exitAfterOutputTimeoutMs}ms after emitting output` + : `${params.label} did not emit output within ${outputTimeoutMs}ms`; reject(new Error(`${timeoutMessage}\nstdout:\n${stdout}\nstderr:\n${stderr}`)); return; } @@ -193,6 +201,7 @@ async function runHooksRelay(params: { event: "post_tool_use" | "pre_tool_use"; "--timeout", "50", ], + completion: params.event === "post_tool_use" ? "exit" : "output-then-exit", label: `hooks relay ${params.event}`, env: { LINGER_MARKER: fixture.markerPath, @@ -239,6 +248,7 @@ describe("hooks CLI process lifecycle", () => { "--timeout", "5000", ], + completion: "exit", label: "hooks relay explicit state database", env: { OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1", @@ -260,6 +270,7 @@ describe("hooks CLI process lifecycle", () => { // bootstraps sequential so low-core shards test lifecycle, not startup contention. const listResult = await runHooksCli({ args: ["hooks", "list", "--json"], + completion: "output-then-exit", label: "hooks list", env: { LINGER_MARKER: fixture.markerPath,