fix(core): stop the bash tool from hanging forever when a child holds the pipe - #349
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".
|
@qorzj Could a maintainer approve this PR's pending workflow run? The The change is a focused fix in It now settles unconditionally 2s after the timeout kill and 2s after the child's Tests (local): Thanks! |
Problem
Bash calls previously completed only on the child process's
closeevent. A command such assleep 30 & printf 'hi\n'can exit while a descendant retains stdout or stderr, preventing the tool call from returning promptly. Killing the shell does not guarantee pipe EOF: Unix process groups can outlive their leader, while Windows taskkill may fail once the parent PID is gone.A cached working directory that has been deleted or converted to an invalid native path also causes subsequent Bash launches to fail.
Changes
Following the separation of process completion and bounded output draining in Codex's
codex-rs/core/src/exec.rs:closearrives sooner.closepath.run_in_background: trueinterface.builtin pwd -Wso virtual mounts are resolved to native paths. Tests usefs.realpathSync.nativeto normalize Windows 8.3 aliases (RUNNER~1) and long names (runneradmin) before comparison.Tool parameters and public types are unchanged. Explicit background execution retains its existing behavior. Closing inherited pipes can affect descendants that continue writing; this change does not guarantee those descendants are unaffected.
Validation
Latest commit
698d9e0: CI run 34947474973 passed all six jobs (Node 22/24 on Windows, macOS, and Ubuntu), including the Windows cwd retention/fallback and virtual-mount regressions.npm run test --workspace @vegamo/deepcode-core: 335 passed, 1 Windows-only test skipped, 0 failures on macOS.npm run typecheck --workspace @vegamo/deepcode-core: passed.