Summary
On macOS kill(-pgid, 0) returns ESRCH for an orphaned process group whose only remaining members are TERM-resistant children, so wait_for_process_group_exit believed the group had exited and terminate_worker returned early, letting SIGKILL follow a vanished leader.
The macOS branch of process_group_alive now enumerates group members with pgrep -g (Apple's ps -g is a no-op unless unix2003 compat mode is active) and requires at least one live member judged with the existing macos_process_state_allows_escalation stat semantics; a failed or empty query fails closed. The Linux kill(-pgid, 0) + EPERM path is unchanged.
Area
src/apps/cli/src/dispatch/runner.rs (process_group_alive only). Crate: bitfun-cli.
Reproduction or evidence
At 32f2427, process_group_alive (:206-215) is a single kill(-pgid,0) probe with no macOS branch; macos_process_state_allows_escalation (:262-269) already encodes live-state/no-exit-modifier semantics used by process_alive. Test: cancellation_does_not_escalate_after_term_exits_the_verified_leader fails when the leader is reaped while TERM-resistant children survive.
Environment
macOS runners (Apple ps -g is a no-op unless unix2003 compat mode is active; pgrep -g is the reliable PGID probe); baseline 32f2427. A Windows host cannot compile the macOS face, so verification of that face relies on the remote CI CLI Tests(macos) run for the corresponding PR; unix-only tests are cfg-excluded on Windows locally.
AI-assisted change. Testing: verified locally (cargo check -p bitfun-cli exit 0; cargo test --bin bitfun dispatch::runner 7 passed, 0 failed); macOS face covered by remote CI CLI Tests(macos).
Summary
On macOS kill(-pgid, 0) returns ESRCH for an orphaned process group whose only remaining members are TERM-resistant children, so wait_for_process_group_exit believed the group had exited and terminate_worker returned early, letting SIGKILL follow a vanished leader.
The macOS branch of process_group_alive now enumerates group members with pgrep -g (Apple's ps -g is a no-op unless unix2003 compat mode is active) and requires at least one live member judged with the existing macos_process_state_allows_escalation stat semantics; a failed or empty query fails closed. The Linux kill(-pgid, 0) + EPERM path is unchanged.
Area
src/apps/cli/src/dispatch/runner.rs (process_group_alive only). Crate: bitfun-cli.
Reproduction or evidence
At 32f2427, process_group_alive (:206-215) is a single kill(-pgid,0) probe with no macOS branch; macos_process_state_allows_escalation (:262-269) already encodes live-state/no-exit-modifier semantics used by process_alive. Test: cancellation_does_not_escalate_after_term_exits_the_verified_leader fails when the leader is reaped while TERM-resistant children survive.
Environment
macOS runners (Apple ps -g is a no-op unless unix2003 compat mode is active; pgrep -g is the reliable PGID probe); baseline 32f2427. A Windows host cannot compile the macOS face, so verification of that face relies on the remote CI CLI Tests(macos) run for the corresponding PR; unix-only tests are cfg-excluded on Windows locally.
AI-assisted change. Testing: verified locally (
cargo check -p bitfun-cliexit 0;cargo test --bin bitfun dispatch::runner7 passed, 0 failed); macOS face covered by remote CI CLI Tests(macos).