Skip to content

fix(tools): run_shell process-group kill + background mode; propagate cancellation - #36

Merged
ayman3000 merged 1 commit into
mainfrom
fix/shell-tool-hang-and-cancellation
Jul 29, 2026
Merged

fix(tools): run_shell process-group kill + background mode; propagate cancellation#36
ayman3000 merged 1 commit into
mainfrom
fix/shell-tool-hang-and-cancellation

Conversation

@ayman3000

Copy link
Copy Markdown
Owner

Problem

run_shell hung forever on long-lived commands (e.g. npm startng serve). The dev-server grandchild inherited the stdout pipe, so terminating only the direct zsh left the grandchild holding the pipe open → readDataToEndOfFile() never saw EOF → execute() never returned. The 120s timeout fired but killed the wrong process, and the app's Stop button (a between-turns flag) couldn't reach a mid-tool hang.

Fix

  • Process group + kill-tree: launch via posix_spawn with POSIX_SPAWN_SETPGROUP; on timeout or cancellation, SIGKILL the entire group so the read unblocks and execute() always returns.
  • Cancellation-aware: withTaskCancellationHandler kills the group when the task is cancelled (Stop).
  • fd hygiene: pipe fds are now close-on-exec, so concurrent spawns don't inherit each other's write-end (which otherwise defeats EOF).
  • Background mode: background: true (auto-detected for npm start, ng serve, vite, next dev, …) launches detached, redirects output to a temp log, and returns promptly with the pid + log path — servers no longer block the agent by design.
  • ToolDispatcher: parallel tools now run in a structured withTaskGroup instead of detached Task {}, so run-task cancellation propagates into running tools (parallelism + ordering preserved).

Tests

New tests reproduce the exact failure and its fix:

  • shellTimesOutEvenWhenOrphanHoldsStdout — orphan-holds-stdout hang: 30s → ~2s, reported as a timeout.
  • shellBackgroundReturnsPromptlyWithPid — background returns within the grace window with a pid.
  • shellCancellationStopsPromptly — cancellation SIGKILLs and returns in ~0.4s.

Full suite: 134/134 passing.

🤖 Generated with Claude Code

… cancellation

run_shell hung forever on long-lived commands (e.g. `npm start` → `ng serve`):
the dev-server grandchild inherited the stdout pipe, so terminating only the
direct zsh left it holding the pipe open and readDataToEndOfFile never saw EOF —
the 120s timeout fired but killed the wrong process, and Stop couldn't reach it.

- Launch commands in their own process group (posix_spawn + POSIX_SPAWN_SETPGROUP)
  and SIGKILL the whole group on timeout or cancellation, so the read unblocks and
  execute() always returns.
- Make ShellTool cancellation-aware via withTaskCancellationHandler.
- Mark pipe fds close-on-exec to stop concurrent spawns inheriting each other's
  write-end (which defeated EOF).
- Add `background: true` (auto-detected for npm start/ng serve/vite/etc.): launch
  detached, redirect output to a temp log, return promptly with pid + log path.
- ToolDispatcher: run parallel tools in a structured withTaskGroup instead of
  detached Task {}, so run-task cancellation propagates into running tools.

Tests reproduce the 30s hang (now ~2s), the background fast-return, and prompt
cancellation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@ayman3000
ayman3000 merged commit e7bbbaa into main Jul 29, 2026
1 check passed
@ayman3000
ayman3000 deleted the fix/shell-tool-hang-and-cancellation branch July 29, 2026 02:11
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.

1 participant