fix(exec): skip default timeout for background sessions

This commit is contained in:
Peter Steinberger
2026-02-22 23:02:17 +01:00
parent 4b0fddc075
commit c677be9d5f
4 changed files with 40 additions and 4 deletions

View File

@@ -267,7 +267,7 @@ export async function runExecProcess(opts: {
notifyOnExitEmptySuccess?: boolean;
scopeKey?: string;
sessionKey?: string;
timeoutSec: number;
timeoutSec: number | null;
onUpdate?: (partialResult: AgentToolResult<ExecToolDetails>) => void;
}): Promise<ExecProcessHandle> {
const startedAt = Date.now();
@@ -504,7 +504,9 @@ export async function runExecProcess(opts: {
}
const reason =
exit.reason === "overall-timeout"
? `Command timed out after ${opts.timeoutSec} seconds`
? typeof opts.timeoutSec === "number" && opts.timeoutSec > 0
? `Command timed out after ${opts.timeoutSec} seconds`
: "Command timed out"
: exit.reason === "no-output-timeout"
? "Command timed out waiting for output"
: exit.exitSignal != null

View File

@@ -142,6 +142,35 @@ test("background exec still times out after tool signal abort", async () => {
});
});
test("background exec without explicit timeout ignores default timeout", async () => {
const tool = createTestExecTool({
allowBackground: true,
backgroundMs: 0,
timeoutSec: BACKGROUND_TIMEOUT_SEC,
});
const result = await tool.execute("toolcall", { command: BACKGROUND_HOLD_CMD, background: true });
expect(result.details.status).toBe("running");
const sessionId = (result.details as { sessionId: string }).sessionId;
const waitMs = Math.max(ABORT_SETTLE_MS + 120, BACKGROUND_TIMEOUT_SEC * 1000 + 120);
const startedAt = Date.now();
await expect
.poll(
() => {
const running = getSession(sessionId);
const finished = getFinishedSession(sessionId);
return Date.now() - startedAt >= waitMs && !finished && running?.exited === false;
},
{
timeout: waitMs + ABORT_WAIT_TIMEOUT_MS,
interval: POLL_INTERVAL_MS,
},
)
.toBe(true);
cleanupRunningSession(sessionId);
});
test("yielded background exec is not killed when tool signal aborts", async () => {
const tool = createTestExecTool({ allowBackground: true, backgroundMs: 10 });
await expectBackgroundSessionSurvivesAbort({

View File

@@ -442,8 +442,12 @@ export function createExecTool(
execCommandOverride = gatewayResult.execCommandOverride;
}
const effectiveTimeout =
typeof params.timeout === "number" ? params.timeout : defaultTimeoutSec;
const explicitTimeoutSec = typeof params.timeout === "number" ? params.timeout : null;
const backgroundTimeoutBypass =
allowBackground && explicitTimeoutSec === null && (backgroundRequested || yieldRequested);
const effectiveTimeout = backgroundTimeoutBypass
? null
: (explicitTimeoutSec ?? defaultTimeoutSec);
const getWarningText = () => (warnings.length ? `${warnings.join("\n")}\n\n` : "");
const usePty = params.pty === true && !sandbox;