diff --git a/src/agents/bash-tools.exec-runtime.ts b/src/agents/bash-tools.exec-runtime.ts index b154bcd891..2af4e4a7f6 100644 --- a/src/agents/bash-tools.exec-runtime.ts +++ b/src/agents/bash-tools.exec-runtime.ts @@ -338,6 +338,25 @@ export async function runExecProcess(opts: { opts.warnings.push(warning); }; + const spawnShellChild = async ( + shell: string, + shellArgs: string[], + ): Promise => { + const { child: spawned } = await spawnWithFallback({ + argv: [shell, ...shellArgs, execCommand], + options: { + cwd: opts.workdir, + env: opts.env, + detached: process.platform !== "win32", + stdio: ["pipe", "pipe", "pipe"], + windowsHide: true, + }, + fallbacks: spawnFallbacks, + onFallback: handleSpawnFallback, + }); + return spawned as ChildProcessWithoutNullStreams; + }; + // `exec` does not currently accept tool-provided stdin content. For non-PTY runs, // keeping stdin open can cause commands like `wc -l` (or safeBins-hardened segments) // to block forever waiting for input, leading to accidental backgrounding. @@ -421,36 +440,12 @@ export async function runExecProcess(opts: { const warning = `Warning: PTY spawn failed (${errText}); retrying without PTY for \`${opts.command}\`.`; logWarn(`exec: PTY spawn failed (${errText}); retrying without PTY for "${opts.command}".`); opts.warnings.push(warning); - const { child: spawned } = await spawnWithFallback({ - argv: [shell, ...shellArgs, execCommand], - options: { - cwd: opts.workdir, - env: opts.env, - detached: process.platform !== "win32", - stdio: ["pipe", "pipe", "pipe"], - windowsHide: true, - }, - fallbacks: spawnFallbacks, - onFallback: handleSpawnFallback, - }); - child = spawned as ChildProcessWithoutNullStreams; + child = await spawnShellChild(shell, shellArgs); stdin = child.stdin; } } else { const { shell, args: shellArgs } = getShellConfig(); - const { child: spawned } = await spawnWithFallback({ - argv: [shell, ...shellArgs, execCommand], - options: { - cwd: opts.workdir, - env: opts.env, - detached: process.platform !== "win32", - stdio: ["pipe", "pipe", "pipe"], - windowsHide: true, - }, - fallbacks: spawnFallbacks, - onFallback: handleSpawnFallback, - }); - child = spawned as ChildProcessWithoutNullStreams; + child = await spawnShellChild(shell, shellArgs); stdin = child.stdin; maybeCloseNonPtyStdin(); } diff --git a/src/agents/bash-tools.process.ts b/src/agents/bash-tools.process.ts index 0c74733f4d..b5966ab79b 100644 --- a/src/agents/bash-tools.process.ts +++ b/src/agents/bash-tools.process.ts @@ -86,6 +86,18 @@ function resolvePollWaitMs(value: unknown) { return 0; } +function failText(text: string): AgentToolResult { + return { + content: [ + { + type: "text", + text, + }, + ], + details: { status: "failed" }, + }; +} + export function createProcessTool( defaults?: ProcessToolDefaults, // oxlint-disable-next-line typescript/no-explicit-any @@ -258,26 +270,10 @@ export function createProcessTool( }, }; } - return { - content: [ - { - type: "text", - text: `No session found for ${params.sessionId}`, - }, - ], - details: { status: "failed" }, - }; + return failText(`No session found for ${params.sessionId}`); } if (!scopedSession.backgrounded) { - return { - content: [ - { - type: "text", - text: `Session ${params.sessionId} is not backgrounded.`, - }, - ], - details: { status: "failed" }, - }; + return failText(`Session ${params.sessionId} is not backgrounded.`); } const pollWaitMs = resolvePollWaitMs(params.timeout); if (pollWaitMs > 0 && !scopedSession.exited) { @@ -521,26 +517,10 @@ export function createProcessTool( case "kill": { if (!scopedSession) { - return { - content: [ - { - type: "text", - text: `No active session found for ${params.sessionId}`, - }, - ], - details: { status: "failed" }, - }; + return failText(`No active session found for ${params.sessionId}`); } if (!scopedSession.backgrounded) { - return { - content: [ - { - type: "text", - text: `Session ${params.sessionId} is not backgrounded.`, - }, - ], - details: { status: "failed" }, - }; + return failText(`Session ${params.sessionId} is not backgrounded.`); } killSession(scopedSession); markExited(scopedSession, null, "SIGKILL", "failed");