diff --git a/src/cli/logs-cli.runtime.test.ts b/src/cli/logs-cli.runtime.test.ts index c2f05b34ff91..5cc726689484 100644 --- a/src/cli/logs-cli.runtime.test.ts +++ b/src/cli/logs-cli.runtime.test.ts @@ -49,6 +49,18 @@ describe("execFileUtf8Tail", () => { expect(result.truncated).toBe(false); }); + it("terminates a stalled command at the configured deadline", async () => { + const startedAt = Date.now(); + + const result = await execFileUtf8Tail(process.execPath, ["-e", "setTimeout(() => {}, 1_000)"], { + maxBytes: 1024, + timeoutMs: 25, + }); + + expect(Date.now() - startedAt).toBeLessThan(500); + expect(result).toMatchObject({ code: 124, stdout: "", truncated: false }); + }); + it("returns a soft failure when command launch fails", async () => { const command = `openclaw-missing-${process.pid}-${Date.now()}`; const result = await execFileUtf8Tail(command, [], { maxBytes: 1024 }); diff --git a/src/cli/logs-cli.runtime.ts b/src/cli/logs-cli.runtime.ts index d7306aceff1f..eebb13e17ac9 100644 --- a/src/cli/logs-cli.runtime.ts +++ b/src/cli/logs-cli.runtime.ts @@ -7,17 +7,19 @@ export { readSystemdServiceRuntime } from "../daemon/systemd.js"; type ExecFileTailResult = { stdout: string; stderr: string; code: number; truncated: boolean }; +const DEFAULT_LOG_SUBPROCESS_TIMEOUT_MS = 10_000; const STDERR_MAX_BYTES = 64 * 1024; export async function execFileUtf8Tail( command: string, args: string[], - options: { env?: NodeJS.ProcessEnv; maxBytes: number }, + options: { env?: NodeJS.ProcessEnv; maxBytes: number; timeoutMs?: number }, ): Promise { try { const result = await runCommandWithTimeout([command, ...args], { baseEnv: options.env, maxOutputBytes: { stdout: options.maxBytes, stderr: STDERR_MAX_BYTES }, + timeoutMs: options.timeoutMs ?? DEFAULT_LOG_SUBPROCESS_TIMEOUT_MS, }); return { stdout: result.stdout,