Skip to content

fix(bash): kill the command's whole process group on timeout, cancel and drop - #278

Merged
yuanhao merged 3 commits into
mainfrom
fix/bash-process-group
Oct 9, 2026
Merged

yuanhao merged 3 commits into
mainfrom
fix/bash-process-group

Conversation

@yuanhao

@yuanhao yuanhao commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Closes #277 (suggested by @shahidcodes).

Before: a timeout or cancel killed only the bash process (kill_on_drop); pipeline stages, && lists and background jobs kept running.

Now (Unix): bash is spawned with process_group(0). A GroupKill guard killpgs the whole group with SIGKILL on timeout, cancel, or when the call's future is dropped. It is declared after the child, so it fires before the child is reaped — the pgid cannot have been reused. When the command finishes on its own, the guard is disarmed: a detached background job it started deliberately (a dev server) survives. Only a process that calls setsid escapes. Windows is unchanged.

Dependency: libc under cfg(unix) (already in the tree through tokio). This is the crate's first unsafe block — one killpg call with a SAFETY comment.

Tests (tests/tools_test.rs, Unix): timeout, cancel, and drop each kill a background job and a pipeline; a finished command leaves its detached job alone. Mutation-checked: without process_group(0) the three kill tests fail; without disarm() the last one fails. Full suite: 1516 passed. Docs: docs/concepts/tools.md caveat, CLAUDE.md, CHANGELOG.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG

yuanhao and others added 3 commits October 9, 2026 12:17
…and drop

On Unix, bash leads its own process group (process_group(0)); a GroupKill
guard, declared after the child so it fires before the child is reaped,
killpg's the group on timeout, cancel or drop and is disarmed when the
command finishes on its own. Pipeline stages, && lists and background jobs
no longer outlive a cancelled call; a setsid process still escapes.
Windows unchanged. libc is a cfg(unix) dependency (already in the tree).

Closes #277. Suggested by @shahidcodes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
…se docs

- std's CommandExt::process_group (Rust 1.64) instead of tokio's, which
  needs tokio 1.40 while the crate asks for tokio = "1".
- The command is outside the terminal's foreground group, so the terminal's
  Ctrl+C no longer reaches it: examples/cli.rs now aborts the run on Ctrl+C
  during a run (exits only at the prompt); documented, with /dev/tty prompts.
- Docs: a background job survives a finished command only if its output is
  redirected; one holding the pipes keeps the call open until the timeout.
- Cargo.toml: the unix block no longer splits the wasm32 comment.
- Tests: a zombie counts as gone (no init in a container).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
@yuanhao
yuanhao merged commit c62ba07 into main Oct 9, 2026
15 checks passed
@yuanhao
yuanhao deleted the fix/bash-process-group branch October 9, 2026 11:34
@yuanhao yuanhao mentioned this pull request Oct 9, 2026
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.

BashTool: kill the whole process group on timeout and cancel

1 participant