fix(desktop): detach managed agents from the controlling terminal (setsid)#2321
Open
matthiasdebernardini wants to merge 2 commits into
Open
fix(desktop): detach managed agents from the controlling terminal (setsid)#2321matthiasdebernardini wants to merge 2 commits into
matthiasdebernardini wants to merge 2 commits into
Conversation
…tsid) When the desktop app is launched from a terminal (`just dev`), the managed agent harness subtree (buzz-acp -> claude-agent-acp -> engine + MCP servers) inherited the terminal's controlling TTY. Running as background process groups of that session, macOS job control intermittently STOPPED the actively-working agent's process group (SIGTTIN/SIGTTOU-class stops, observed as `T` state in ps), freezing agent turns indefinitely. Packaged .app launches have no controlling TTY and were unaffected. Spawn the harness via `pre_exec(setsid())` on unix so the whole agent tree runs in its own session with NO controlling terminal, making the freeze impossible. This replaces the previous `process_group(0)`: setsid makes the child both session AND process-group leader (pid == pgid), so the existing `-pid` group-kill paths still reach the entire tree. We drop `process_group(0)` because std may apply setpgid before pre_exec runs, which would make setsid fail EPERM. If setsid does fail, we fall back to a best-effort setpgid(0, 0) and never fail the spawn. Adds a unix test asserting the spawned child is a session leader in a session distinct from the test process. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCWMx2BkoFDZtFRiDny16W
Resolutions: storage.rs takes main (write_agent_pid_file was replaced by runtime receipts); runtime/tests.rs keeps both the new setsid session-leader test and main's receipt validation tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019DHNgyFUB6276FGHfBQA4a
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
When the desktop app is launched from a terminal (
just dev), every managed-agent process it spawns — thebuzz-acpharness, theclaude-agent-acpadapters, the agent engine, and its MCP servers — inherits the terminal's controlling TTY. The harness was spawned withprocess_group(0), which makes the subtree a background process group of that terminal session. macOS job control then intermittently stops the actively-working agent's process group with SIGTTIN-class stop signals.Observed live: an agent turn froze mid-
Bashtool call with the engine, both its MCP servers, and finally the adapter all inpsstateT(stopped). The turn hangs indefinitely; the desktop shows the agent "working" forever (the typing/status indicator loops), and the reply only escapes if someone manuallykill -CONTs the process group. Packaged.applaunches have no controlling TTY and are unaffected — this only bites development runs, but it makes agents unusable underjust dev.Fix
Replace
process_group(0)with apre_execsetsid()inspawn_agent_child(the sole long-lived harness spawn site).setsid()puts the whole agent subtree in its own session with no controlling terminal, so background-TTY job control can never stop it. Ifsetsid()fails (EPERM when already a group leader), fall back tosetpgid(0, 0)best-effort — exactly the pre-patch behavior — and never fail the spawn.Invariants preserved:
setsid()makes the child both session and process-group leader, sopid == pgidstill holds and every existing kill path (sigterm_then_sigkill, orphan-sweepkill(-pid, …)) is unchanged. The orphan-cleanup comment even anticipated setsid'd children already.BUZZ_MANAGED_AGENTmarker + PID-receipt sweep was already the safety net (the harness never sat in the shell's job list even before this change).Job Object) path untouched;#[cfg]gating mirrors the code it replaces.The
unsafeinpre_execis minimal (only async-signal-safesetsid/setpgid) and follows the existinglibc::killprecedent in this file. Stale comments instorage.rs/process_lifecycle.rsthat citedprocess_group(0)as the pid==pgid mechanism are updated.Tests
#[cfg(unix)]testspawned_harness_is_session_leader_in_new_session: asserts a child spawned with the samepre_execbecomes a session leader (sid == pid) in a session distinct from the test process, withpgid == pidso the group-kill invariant holds. (It exercises the setsid pattern on a stand-in child —spawn_agent_childneeds anAppHandle— see the test doc comment for the limitation.)cargo test --manifest-path desktop/src-tauri/Cargo.toml managed_agents— 682 passed.cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets -- -D warnings— clean;cargo fmtapplied.just dev(agent turn stuck with adapter/engine/MCP in stateT), and verifiedkill -CONTon the stopped group let the turn complete — confirming background-TTY stops as the mechanism this patch removes.🤖 Generated with Claude Code
https://claude.ai/code/session_01PCWMx2BkoFDZtFRiDny16W