From a14d275b2a6acb94e326585764795f033745928d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 05:39:55 +0000 Subject: [PATCH] refactor(agents): dedupe exec spawn fallback wiring --- src/agents/bash-tools.exec-runtime.ts | 56 ++++++++++----------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/src/agents/bash-tools.exec-runtime.ts b/src/agents/bash-tools.exec-runtime.ts index 57dcdb6bb91..b154bcd8910 100644 --- a/src/agents/bash-tools.exec-runtime.ts +++ b/src/agents/bash-tools.exec-runtime.ts @@ -324,6 +324,20 @@ export async function runExecProcess(opts: { let stdin: SessionStdin | undefined; const execCommand = opts.execCommand ?? opts.command; + const spawnFallbacks = [ + { + label: "no-detach", + options: { detached: false }, + }, + ]; + + const handleSpawnFallback = (err: unknown, fallback: { label: string }) => { + const errText = formatSpawnError(err); + const warning = `Warning: spawn failed (${errText}); retrying with ${fallback.label}.`; + logWarn(`exec: spawn failed (${errText}); retrying with ${fallback.label}.`); + opts.warnings.push(warning); + }; + // `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. @@ -359,18 +373,8 @@ export async function runExecProcess(opts: { stdio: ["pipe", "pipe", "pipe"], windowsHide: true, }, - fallbacks: [ - { - label: "no-detach", - options: { detached: false }, - }, - ], - onFallback: (err, fallback) => { - const errText = formatSpawnError(err); - const warning = `Warning: spawn failed (${errText}); retrying with ${fallback.label}.`; - logWarn(`exec: spawn failed (${errText}); retrying with ${fallback.label}.`); - opts.warnings.push(warning); - }, + fallbacks: spawnFallbacks, + onFallback: handleSpawnFallback, }); child = spawned as ChildProcessWithoutNullStreams; stdin = child.stdin; @@ -426,18 +430,8 @@ export async function runExecProcess(opts: { stdio: ["pipe", "pipe", "pipe"], windowsHide: true, }, - fallbacks: [ - { - label: "no-detach", - options: { detached: false }, - }, - ], - onFallback: (fallbackErr, fallback) => { - const fallbackText = formatSpawnError(fallbackErr); - const fallbackWarning = `Warning: spawn failed (${fallbackText}); retrying with ${fallback.label}.`; - logWarn(`exec: spawn failed (${fallbackText}); retrying with ${fallback.label}.`); - opts.warnings.push(fallbackWarning); - }, + fallbacks: spawnFallbacks, + onFallback: handleSpawnFallback, }); child = spawned as ChildProcessWithoutNullStreams; stdin = child.stdin; @@ -453,18 +447,8 @@ export async function runExecProcess(opts: { stdio: ["pipe", "pipe", "pipe"], windowsHide: true, }, - fallbacks: [ - { - label: "no-detach", - options: { detached: false }, - }, - ], - onFallback: (err, fallback) => { - const errText = formatSpawnError(err); - const warning = `Warning: spawn failed (${errText}); retrying with ${fallback.label}.`; - logWarn(`exec: spawn failed (${errText}); retrying with ${fallback.label}.`); - opts.warnings.push(warning); - }, + fallbacks: spawnFallbacks, + onFallback: handleSpawnFallback, }); child = spawned as ChildProcessWithoutNullStreams; stdin = child.stdin;