Conversation
…ard (dora-rs#3472) `path: shell` nodes run `sh -c <args>` with no dora code in them, so the in-node `DORA_RUN_PARENT_PID` orphan guard never arms. They were contained only by the daemon's spawn-time PDEATHSIG, which reaches the shell alone: a shell that forks a background child (`sh -c 'sleep 1000 & wait'`) leaks that child to `ppid 1` when `dora run` is SIGKILLed. The daemon now routes unix shell spawns through a hidden `dora __shell-guard` subcommand that becomes the daemon's direct child AND the process-group leader. It spawns the shell inside that group and, once `DORA_RUN_PARENT_PID` is gone, `killpg`s the whole group — guard, shell, and background forks together — mirroring the in-node `orphan_guard`. On the coordinator-attached path (`dora up` + `dora start`) the env var is absent and the guard is a transparent passthrough that forwards the shell's exit status, preserving dora-rs#2029. Windows `cmd /C` is unchanged. The CLI's `libc` dependency becomes non-optional (previously gated behind the `redb-backend` feature) for the guard's `getppid`/`getpgrp`/`killpg`/`prctl` calls. Adds an e2e test that SIGKILLs `dora run` running a shell node which publishes the shell and background-child pids and asserts both are gone within 30s; confirmed to fail against a daemon that spawns `sh` directly (only the shell, not the child, is contained) and pass with the guard.
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
🤖 Automated review (Claude) — first review of this PR. Fully automated, no human in the loop; advisory only, not an approval (I can't approve or merge). Reviewed from the diff/code as source of truth, not the description. No blocking issues found. I verified the load-bearing pieces:
One non-blocking simplification. The guard is inserted on the coordinator-attached ( Minor edge note (also non-blocking): containment only holds while the shell stays alive. A shell that forks a background child and then exits immediately ( Generated by Claude Code |
Review follow-up (dora-rs#3472): the guard was inserted on the coordinator-attached (`dora up` + `dora start`) path too, where `DORA_RUN_PARENT_PID` is absent so the guard could never arm - a pure passthrough that added a resident `dora` process per shell node and bought no containment (those nodes are meant to outlive the daemon, dora-rs#2029). Gate the guard insertion on `Spawner::bind_nodes_to_parent`, the same signal the pre-`init` orphan window in prepared.rs and the generator Windows KillOnDrop (dora-rs#3544) use. On the coordinator-attached path the daemon now spawns `sh -c` directly. The `dora run` path is unchanged and still routed through `dora __shell-guard`. Verified: the run-path e2e (run_killed_by_sigkill_does_not_orphan_shell_nodes) still passes; a shell node under `dora start` runs plainly with no guard process; fmt + clippy clean.
|
Addressed the non-blocking suggestion: the shell guard is now scoped to the in-process
The
|
|
🤖 Automated review (fully automated Claude Code review — no human vetted this; advisory only, not an approval, and I can't approve or merge). Reviewed from the diff. New commit This won't compile for Windows targets. In
None of these exist in libc's Windows bindings, so Suggest gating the Otherwise the mechanism holds up: Generated by Claude Code |
Windows build fix from review: the `shell_guard` module, the `Command::ShellGuard` variant/execute arm, and the `parse_shell_guard` test were compiled on every target, but the guard body uses unix-only libc symbols (getppid, kill/ESRCH, killpg, getpgrp, SIGKILL, _exit) and has no Windows `clear_parent_death_signal` definition - so `cargo check -p dora-cli` would fail for the windows targets in the nightly matrix. Gate all of it behind `#[cfg(unix)]`, matching the daemon side (dora_guard_command already returns None on non-unix). Windows shell spawns go through `cmd /C` unchanged and never reference the guard. Linux build/clippy/fmt clean; parse_shell_guard passes.
|
Fixed the Windows-compile blocker: Verified: |
|
🤖 Automated review (fully automated Claude Code review — no human vetted this; advisory only, not an approval, and I can't approve or merge). Reviewed from the diff. Re-reviewed at Generated by Claude Code |
|
@phil-opp Could you proceed with this merge? Claude re-review is clean (no blocking issues) and all CI checks are green. If it's in the queue for trunk, please submit it — or let me know if the author-side |
|
🤖 Automated review by Claude — fully automated review; no human has vetted this. Advisory only, posted like an outside contributor — I can't approve or merge. One blocking issue that the earlier passes and PR CI didn't surface (the The new hidden Worth deciding how to resolve rather than just regenerating the snapshot: Generated by Claude Code |
… snapshot Merge-queue surface-snapshot fix from review: `cli_surface_snapshot_is_current` walks `get_subcommands()` with no hidden-filter, so the new unix-only `dora __shell-guard` spawn wrapper leaked into the generated surface (`dora __shell-guard` + its positional `<command>`) and the snapshot test panicked in the merge-queue Test run (PR CI shows Test as skipped -- it runs on the queue head, not the PR head). Skip subcommands whose name starts with `__` in the walk instead of recording the guard into `cli-surface.txt`: pinning an internal implementation detail into the frozen 1.0 surface would be a pseudo-breaking change on every non-unix platform. Note the exclusion in the snapshot header.
|
Addressed the blocking surface-snapshot issue — new head
Verified: |
|
🤖 Automated review by Claude — fully automated review, no human vetted this; advisory only, not an approval. Re-reviewed at Generated by Claude Code |
Summary
Closes #3472.
path: shellnodes runsh -c <args>with no dora code in them, so the in-nodeDORA_RUN_PARENT_PIDorphan guard never arms. They were contained only by the daemon's spawn-timePR_SET_PDEATHSIG, which reaches the direct child alone: a shell that forks a background child (sh -c 'sleep 1000 & wait') leaks that child toppid 1whendora runis SIGKILLed.Unix shell spawns are now routed through a hidden
dora __shell-guardCLI subcommand, spawned as the daemon's direct child and process-group leader. On thedora runpath it arms on the injectedDORA_RUN_PARENT_PIDand, once that process is gone,killpgs its whole group — guard, shell, and background forks together — mirroring the in-nodeorphan_guard(it also clears the daemon's PDEATHSIG once its own 500ms poll is running, exactly like the in-node guard does). WhenDORA_RUN_PARENT_PIDis absent (dora up+dora start) it is a transparent passthrough that waits for the shell and forwards its exit status, preserving #2029. Windowscmd /Cis unchanged.Validation
run_killed_by_sigkill_does_not_orphan_shell_nodes: a shell node publishes the shell's pid and asleep 1000 &background child's pid,dora runis SIGKILLed, and both must be gone within 30s. Verified it fails without the fix (only the shell is PDEATHSIG-contained; the child survives) and passes with it.run_killed_by_sigkill_does_not_orphan_nodes,run_killed_before_node_init_does_not_orphan,run_killed_by_sigterm_terminates_nodes_and_exits.smoke_shell_node_allowed_with_flag/smoke_shell_node_blocked_without_flagstill pass (gate intact).cargo clippy -p dora-cli -p dora-daemon --all-targets -- -D warnings,cargo fmt --all -- --check,cargo test -p dora-cli --lib(420) and-p dora-daemon --lib(280) all clean.shutdown.rs), which now simply includes the guard as leader.