[rush-daemon-transport] Reap a dead daemon's orphaned operation processes on reclaim - #6088
Conversation
…sses on reclaim Fixes #6055 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Linux e2e validation (WSL Ubuntu-24.04, synthetic 12-project workspace)Repro: start
After-run log line: (SIGTERM sufficed; no SIGKILL escalation needed.) Script: |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Current rushd operations use separate detached process groups, so targeting only the dead daemon’s group does not reap the actual orphaned operations.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (5)
What changed in this PR
Adds stale-daemon process cleanup during socket reclamation.
Changes:
- Adds guarded POSIX process-group termination with SIGTERM/SIGKILL escalation.
- Integrates orphan reaping into daemon reclaim.
- Adds unit and process-level tests plus a patch change record.
| File | Description |
|---|---|
DaemonReclaim.ts |
Invokes orphan cleanup before removing artifacts. |
DaemonProcessGroup.ts |
Implements injectable process-group operations. |
DaemonOrphanReaper.ts |
Adds guarded termination and escalation logic. |
DaemonOrphanReaper.test.ts |
Tests reaper outcomes and safety checks. |
OrphanReapOnReclaim.test.ts |
Adds process-level reclaim coverage. |
rushd-reap-orphans_2026-09-24-03-00.json |
Records the patch change. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…unknown own group, and SIGKILL survivors Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>



Summary
When a
rushddaemon dies uncleanly (SIGKILL / OOM killer), the phased operation child processes it spawned keep running. They share the daemon's session and process group (sid = pgid = daemon pid, because the daemon is spawned detached andShellOperationRunnerchildren are not). The auto-started successor then reclaims the socket and re-runs the same operation concurrently on the same outputs as the orphan. This PR makes reclaim terminate those orphans before the successor does any work.Root cause
reclaimStaleDaemonAsyncproved the owner dead (dead lockfile PID plus a failed connect probe) and removed the lockfile and socket. It did nothing about the processes the dead daemon left behind in its process group.Fix
DaemonOrphanReaper.reapDeadDaemonProcessGroupAsync(deadPid, options?), called fromreclaimUnderLockAsync. It runs under the reclaim mutex, after the two-factor dead-owner proof, and before the artifacts are removed and the successor binds. It:SIGTERMto process groupdeadPidand polls for up to a bounded grace period (2 s);SIGKILLand polls again for up to the same grace period;process.emitWarning(..., { code: 'RUSH_DAEMON_ORPHANS_REAPED' }), e.g.Reclaimed dead daemon 1234: its orphaned operation process group was terminated|killed.deadPidis ever signaled. That is the proven-dead daemon's pid recorded in the lockfile, and it is signaled only whendeadPidis not alive and groupdeadPidstill exists. POSIX never assigns a pid that is still in use as a process group id, so no unrelated process can have takendeadPidwhile that group exists, and every remaining member belongs to the dead daemon.< 2(kill(-0)/kill(-1)), non-integers, the caller's own pid, or the caller's own process group (read from/proc/self/stat). Does nothing when the caller's group cannot be determined (no/proc) or on Windows. OnlyESRCHcounts as "group gone";EPERMand other probe or signal errors propagate and fail the reclaim.IDaemonProcessGroupOps(DaemonProcessGroup.ts). There is no public API change: the new modules are not exported from the package index.This is only the orphan-reaping part of the A05 prototype.
Tests
DaemonOrphanReaper.test.ts(fake ops) covers:DaemonProcessGroup.test.ts:ESRCHmeans gone,EPERMpropagates, and the signal is sent to the negated group id.OrphanReapOnReclaim.test.ts(POSIX, real processes): a detached fake daemon with one non-detached child (like a phased operation) is SIGKILLed, andreclaimStaleDaemonAsyncreaps the orphaned child and logs it.Linux validation (WSL Ubuntu-24.04)
rush build --to @rushstack/rush-daemon-transportandrush test --only @rushstack/rush-daemon-transportpass, including the package's ultra-strict lint contract.kill -9the daemon during a 30 sbuild --only p01, then run the next build. Details are in the PR comment.sh -c node build.js,node build.js) were still running after the next build.RUSH_DAEMON_ORPHANS_REAPEDwarning was logged.Out of scope / follow-ups
IPCOperationRunner, all viaSubprocessTerminator.RECOMMENDED_OPTIONS. Covering them needs@rushstack/rush-daemonto persist those group ids, plus a PID-reuse guard for groups whose leader may have exited (for example, comparing start times)./proc, so reaping is currently disabled there (fail closed).rush-clientoutput explicitly.Fixes #6055
This fix came out of the automated rushd Linux analysis ("Rushd Hive").