Skip to content

fix(shell): return when the shell exits even if a background process holds its output - #194

Merged
akemmanuel merged 1 commit into
akemmanuel:masterfrom
touch2be:fix/shell-background-hang
Nov 12, 2026
Merged

akemmanuel merged 1 commit into
akemmanuel:masterfrom
touch2be:fix/shell-background-hang

Conversation

@touch2be

Copy link
Copy Markdown
Collaborator

Problem

A Session hung for about an hour on a single shell call. The command started a local preview server in the background:

(cd /tmp/preview && setsid nohup python … > log 2>&1 & echo PID $!); sleep 4; curl …

The server redirected its own output, but bash forks a wrapper shell for the async cd … && setsid nohup … list. That wrapper inherits the tool's stdout/stderr pipes and waits for the server. So:

  1. The top-level shell exited normally. The tool kept waiting for close, which only fires when every holder of the pipes has closed them, so the wait never ended.
  2. The timeout and the Stop button had no effect. terminateProcessTree returns early once child.exitCode !== null, and stop() then waited on the same close event again.

Minimal reproduction: sleep 8 & echo ready takes 8 s instead of returning at once. With a server instead of sleep, it never returns.

Fix

Once the shell has exited, the tool waits a 500 ms drain grace for remaining output. It then destroys its stdout/stderr readers and returns the result. The result includes a notice that a background process still holds the output and how to redirect it. The background process keeps running, so intentionally started servers still work.

truncated and the output budget now account for the notice, so the result stays inside the 4 KiB bound.

Tests

Added to shell.test.ts (written first; both failed before the fix):

  • returns when the command exits even if a background process keeps its output open (sleep 8 & …; echo ready → < 3 s, output contains the notice)
  • stops at its timeout even if a detached process keeps its output open (setsid sleep 8 & …; sleep 30, timeout 0.2 s → timedOut, < 3 s)

vp check passes and all 291 harness tests pass. I also checked the exact command pattern from the hung Session by hand: it now returns in about 1 s, and the server keeps running.

…holds its output

A command that starts a background process without fully redirecting its
output (e.g. `cd dir && setsid nohup server > log &`, where the async list's
wrapper shell keeps stdout) left the shell tool waiting for the pipes to
close. Because the shell itself had already exited, the timeout and the
run's abort signal were no-ops too, so the Session hung indefinitely.

After the shell exits, wait a short drain grace, then stop reading, return
the result and tell the model that a background process still holds the
output. The background process keeps running.
@akemmanuel
akemmanuel merged commit d601df8 into akemmanuel:master Nov 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants