[rush-daemon] Run rushx scripts without exclusive admission and complete on child exit - #6091
Merged
Sean Larkin (TheLarkInn) merged 4 commits intoSep 24, 2026
Conversation
…ete on child exit Fixes #6085 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Windows cannot recover a child's descendants after it exits, so closing pipes remain the only signal that native install/update workers have finished. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Context-wide output progress can prevent an exited child’s drain timeout when another child continues producing output.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Updates rushd so Rushx scripts bypass workspace admission and complete safely when their direct child exits.
Changes:
- Bypasses admission for explicit Rushx invocations.
- Adds POSIX child-exit completion, bounded output draining, and process-group termination.
- Updates integration tests for non-queued Rushx behavior.
| File | Description |
|---|---|
libraries/rush-daemon/src/GlobalCommandExecutionContext.ts |
Implements child-exit cleanup and output draining. |
libraries/rush-daemon/src/GlobalCommandRequestRouter.ts |
Skips admission for Rushx requests. |
libraries/rush-daemon/src/GlobalCommandRequest.ts |
Carries resolved invocation kind. |
libraries/rush-daemon/src/DaemonRequestDispatcher.ts |
Propagates invocation kind. |
libraries/rush-daemon/src/test/GlobalCommandRequestRouter.test.ts |
Tests admission, draining, and cancellation. |
apps/rush-cli-client/src/test/RushXDaemonBoundaries.test.ts |
Verifies scripts bypass exclusive requests. |
apps/rush-cli-client/src/test/RushXDaemonAlias.test.ts |
Updates alias behavior coverage. |
apps/rush-cli-client/src/test/pipedInput.test.ts |
Updates queue-flag expectations. |
common/reviews/api/rush-daemon.api.md |
Records the API surface change. |
common/changes/@rushstack/rush-daemon/rushx-admission-6085_2026-09-24.json |
Adds the change description. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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-rushx-exclusive-admission-and-hang
branch
September 24, 2026 21:17
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
Two rushx-via-daemon problems from #6085:
rushxscript took exclusive workspace admission for its whole lifetime. Concurrent scripts ran one after another, and builds queued behind a long script failed with a wait timeout.rushx-clienthung when a script left a background descendant holding stdout/stderr (for example(sleep 120 &); echo started). Also, cancelling after the direct child had exited did not kill the child's process group.Root cause
DaemonRequestDispatcherroutes rushx ascommandOrigin: 'custom'.classifyRushCommandfails closed toExclusivefor non-built-in commands, andGlobalCommandRequestRouter.executeAsyncheld that lease until the script finished.GlobalCommandExecutionContext.#trackChildAsynccompleted only on the child's'close'. That event waits for every holder of the pipes, including background descendants. On abort it calledSubprocessTerminator.killProcessTree, which returns early onceexitCodeis set, so the surviving group was never signalled.Fix
IResolve(d)GlobalCommandRequestnow carriesinvocationKind(the dispatcher sets it from the envelope). Forrushx, the router skipsRequestScheduleradmission.RushXDaemonRequestResolverbefore the router runs, and the script only runs a package script. Nativerushxtakes no workspace lock either.RequestSchedulerserializes different shared classes and would still block builds.assertWorkspaceRequestResourcesHealthyandassertActivestill run before execution. Repository-definedrushcustom commands stayExclusive.'exit'. Its process group is then SIGKILLed right away (this is the existing "owned group" policy; the kill used to happen after'close').forwardOutput: false) is still awaited until'close'or abort.'close'-based completion. Windows cannot recover a child's descendants after the child exits, so the pipes closing is the only signal that, for example, native install/update workers have finished. An earlier revision completed on'exit'on Windows too, andSuccessfulNativeMutationfailed withEBUSYin CI.#terminateChildsignals the process group directly (process.kill(-pid, 'SIGKILL')) when the direct child has already exited, and useskillProcessTreeotherwise.Behaviour change: rushx queue flags
Because rushx scripts no longer take workspace admission,
--no-waitand--wait-timeouthave no effect on a rushx script. They are still forwarded in the request and still never reach the script's argv, but the script no longer queues or fails behind an exclusive Rush request.Three
rush-cli-clienttests were written for the old queueing behaviour, and CI failed on them. I updated them to check the new behaviour:pipedInput: queue flags.RushXDaemonBoundaries: hooks changed while queued.RushXDaemonAlias: alias retargeted while queued.The executor still re-checks config and cwd, as before.
Tests
New tests in
GlobalCommandRequestRouter.test.ts:rushcustom command withnoWaitstill fails admission.-pidand does not callkillProcessTree.The three
rush-cli-clienttests above were updated.rush testfor@rushstack/rush-daemonand@rushstack/rush-cli-clientis green on Linux.Linux validation (WSL Ubuntu-24.04)
rushx-client slow(sleep 10)rushx-client bg((sleep 60 &); echo started)Follow-ups (not in this PR)
Fixes #6085
This came out of the automated rushd Linux analysis (Rushd Hive; board bugs #155, #156).