fn detach_declared_background_session(args: &Args)Expand description
For a --declared-background invocation, detach this process into a
brand-new session (setsid()) before doing anything else, so it has no
controlling terminal at all.
Without this, this process (and anything it spawns — the wrapped
command’s own PTY-hosted child, e.g. task dev) stays in the SAME
session as the claude CLI process that invoked it (an ordinary child
inherits its parent’s session/process group unless it explicitly
detaches). Two independent POSIX mechanisms then both threaten it the
moment that session’s leader (claude) is killed for ANY reason,
including a clean single-PID kill that never touches this process
directly:
- If
claude’s own PTY master is closed (agentmux-srv’s normal stop/replace path drops its input channel, which drops the PTY writer+master — seeblockcontroller/shell/lifecycle.rs), the kernel hangs up the terminal, sending SIGHUP to the whole foreground process group of that terminal. - Independently of (1): POSIX also sends SIGHUP to a session’s
foreground process group when the session LEADER terminates — this
fires purely from
claudeexiting, with no PTY-close step required at all. Either one, by default, terminates this process (SIGHUP’s default disposition) — and if this process is what’s directly holding open the wrapped command’s own PTY master (the common case, viarun_via_pty), this process dying closes that PTY too, cascading the exact same hangup one level deeper to the wrapped command itself.setsid()makes this process (and, transitively, anything it PTY-spawns afterward, which becomes associated with THIS new session rather thanclaude’s) immune to both — a session with no controlling terminal at all has no foreground process group for the kernel to signal. See docs/specs/SPEC_BACKGROUND_TASK_TEARDOWN_SURVIVAL_2026_08_20.md and the Codex finding on PR #2683 this was written to address.
A plain child (not already a process group leader — always true here,
since this process was just exec’d as an ordinary child of claude)
can always call setsid() successfully; a failure would be a genuine
platform anomaly, logged but not fatal — worst case this invocation
keeps the pre-existing (already-buggy, already being fixed elsewhere in
this same PR) exposure, rather than losing the command’s actual output.
No-op on Windows (no session/controlling-terminal concept to detach
from) and for an ordinary, non-backgrounded invocation.