fix(core): stop the bash tool from hanging forever when a child holds the pipe - #349
Open
raymondginger2018-sudo wants to merge 1 commit into
Open
Conversation
… the pipe executeShellCommand only resolved the tool call inside the child's 'close' event. A backgrounded descendant (`cmd &`, `nohup ... &`) inherits the tool call's stdout/stderr pipes and keeps them open after the shell itself exits, so 'close' never fires; the timeout path called killProcessTree(pid) only, which is a no-op once that pid is gone (taskkill /PID <pid> /T /F exits 128) and did not settle the promise either. The promise never settled, so the tool call never returned and the whole CLI session hung permanently (observed 2026-09-13 23:44; the machine had to be hard-powered-off). - settle unconditionally 2s after the timeout kill, and 2s after the child's 'exit' event, destroying the pipes on the way out - ignore a late 'close' once settled, and keep the timeout status instead of letting 'exit' report exitCode 0 for a killed command - append a note telling the model to use run_in_background for detached work - also validate the stored session cwd (fs.statSync().isDirectory()): a poisoned cwd (git-bash /tmp/x stored as \tmp\x) made every later spawn fail with ENOENT, killing the bash tool for the rest of the session Regression test: "Bash settles when a background descendant keeps the output pipe open".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
executeShellCommandonly settled its promise inside the child's'close'event.'close'fires when the process and its stdio pipes are done, so a backgrounded descendant keeps it from ever firing:cmd &,nohup ... &— the&backgrounds the whole&&list, and the detached subshell inherits this tool call'sstdout/stderrpipes.'close'never arrives.killProcessTree(pid)only, which is a no-op once the shell pid is gone (taskkill /PID <pid> /T /Fexits 128), and it did not settle the promise either.Net effect:
resolve()never ran, so the tool call never returned and the whole CLI session wedged permanently. Observed 2026-09-13 23:44; the machine had to be hard-powered-off.A safe-looking command is enough to trigger it:
Fix
'exit'event (a new'exit'handler), destroying the stdout/stderr pipes on the way out.'exit'fires when the process is gone, before/without'close'.'close'once settled, and keep the timeout status instead of letting a late'exit'reportexitCode: 0for a command that was actually killed.run_in_background: truefor detached work, so the next call does not repeat the pattern.Detached work is unaffected: only the tool call settles early; the background process keeps running.
Tests
packages/core/src/tests/tool-handlers.test.ts— new case "Bash settles when a background descendant keeps the output pipe open":sleep 5 &inherits the pipes and outlives the shell. With a 60s configured timeout the call must still return in a few seconds, reportok, include the note, and emit exactly oneonProcessExit.tsc --noEmitis clean.