[rush-daemon] Read Linux process group state from procfs instead of ps - #6087
Merged
Sean Larkin (TheLarkInn) merged 2 commits intoSep 24, 2026
Merged
Sean Larkin (TheLarkInn) merged 2 commits into
Sean Larkin (TheLarkInn) merged 2 commits into
Conversation
Fixes #6072 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sean Larkin (TheLarkInn)
requested review from
Bharat Middha (bmiddha) and
Jonathon Anthony (jxanthony)
as code owners
September 24, 2026 03:06
Sean Larkin (TheLarkInn)
enabled auto-merge (squash)
September 24, 2026 05:04
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Procfs read errors can hide live processes, and process-table reads use unbounded concurrency.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Replaces Rush daemon’s Linux process-group inspection with procfs, retaining ps as a fallback.
Changes:
- Adds injectable procfs-based process-state inspection.
- Expands procfs and fallback test coverage.
- Adds a patch change entry.
| File | Description |
|---|---|
LinuxProcessGroupExit.ts |
Implements procfs inspection and ps fallback. |
LinuxProcessGroupExit.test.ts |
Tests procfs parsing, races, waiting, and fallback. |
NativeMutationCleanupFailure.test.ts |
Preserves fallback failure-path coverage. |
fix-rushd-procfs-process-group-checks_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.
…le entries Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Mo Jazayeri (mojaza)
approved these changes
Sep 24, 2026
Sean Larkin (TheLarkInn)
deleted the
thelarkinn-fix-rushd-procfs-process-group-checks
branch
September 24, 2026 21:18
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.


Summary
On Linux, the daemon checks that a rushx or global command's process group has exited by running
ps --sid <pgid> -o stat=. When procpspsis missing (node:*-slim, distroless, many CI images) or is busybox'sps(which rejects--sid), that check rejects. The rejection is then recorded as a workspace resource cleanup failure. After that, the session is retired, every later request replays the oldFailed to release resources … spawn ps ENOENTerror, anddaemon stopleaves the process running with its listener bound.This PR reads group membership and state directly from
/procand keepspsonly as a fallback for when procfs cannot be listed.Root cause
GlobalCommandExecutionContext#trackChildAsyncSIGKILLs the child's group, then callswaitForLinuxProcessGroupExitAsync. Ifkill(-pgid, 0)still sees the group (the killed members have not been reaped yet), that function execsps. The window is timing-dependent, which is why the bug is intermittent.ENOENTor an unsupported flag rejects the promise.recordWorkspaceRequestCleanupFailurethen retires the session. From that point the host deliberately keeps ownership because it cannot prove the group was joined.Fix
LinuxProcessGroupExit.ts: a new procfs path lists/proc, reads each<pid>/stat, and parses after the last)so command names with spaces or parentheses are handled. It collects field 3 (state) for entries whose field 6 (session) equals the group. Entries that exit between the listing and the read are ignored. The existing zombies-only and timeout semantics are unchanged.waitForLinuxProcessGroupExitAsync(groupId, timeoutMs, procfs: ILinuxProcfsReader). It is internal and not exported from the package index.ps --sidstill runs, but only when procfs cannot be listed. Its failure semantics are unchanged.Based on A10's prototype (board #204).
Tests
LinuxProcessGroupExit.test.ts: the existingpstests now run with an injected "no procfs" reader. New procfs tests:psnever runs) S 1 <sid> <sid>is not misparsedpsthat would fail with ENOENT is never consulted while procfs is readableNativeMutationCleanupFailure.test.tsinjects a hidden procfs, so its injectedpsinspection or timeout failure is still exercised end to end.Linux validation (WSL Ubuntu 24.04, node 22.23.2)
rush build --to @rushstack/rush-daemon --to @rushstack/rush-cli-client: success; the build includes lint.rush test --only @rushstack/rush-daemon: success (1m27s).LinuxProcessGroupExit17/17 andNativeMutationCleanupFailure4/4 pass.waitForLinuxProcessGroupExitAsynccalled on a real, live detached group (sleep 1.5), with a PATH that has nops:REJECTED after 6ms: spawn ps ENOENT. In the daemon, this is the error that poisons the session.resolved after 1516ms, which is the correct wait for the live member.rushx bg(a redirected backgrounded child), thenrushx build, thendaemon stop, with the daemon PATH lackingps. It gave 0/4 failures both before and after, because the race window did not open under the current load. A10 observed the same thing (0/8 later versus 3/3 earlier; board Add three more events: preRushInstall, postRushInstall, preRushBuild. #204). The direct probe above covers the failing path deterministically.Scope and follow-ups (intentionally not in this PR)
WorkspaceRequestResources.ts) anddaemon stopleaving the host alive after a failed join (RushDaemonHost#closeOnceAsync). Both are deliberate safety invariants today: a host that cannot prove an owned group was joined keeps its ownership rather than becoming reclaimable over live children, and existing tests (DaemonShutdownOwnership,NativeMutationCleanupFailure,GlobalCommandRequestRouter) assert this. This PR removes the false-positive trigger (a missing or incompatibleps), so the invariant now fires only when a group is genuinely still live after SIGKILL plus 5s, or cannot be probed (EPERM). Suggested follow-up:daemon stopterminates, or surfaces a clear diagnostic, after a genuine join failureFixes #6072
This came out of the automated rushd Linux analysis ("Rushd Hive").