[rush-client-core] Reclaim provably stale daemon artifacts; make daemon stop/restart idempotent; add daemon stop --force - #6080
Merged
Sean Larkin (TheLarkInn) merged 4 commits intoSep 24, 2026
Conversation
…on stop/restart idempotent Fixes #6061 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Member
Author
|
CI note: the Node.js v24 (windows-latest) failure in run 35947334840 looks like runner flakiness, not this change, so I re-ran the failed job once.
|
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Malformed live-PID records can still wedge startup, and one force-stop invocation may leave the startup reservation behind.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (3)
What changed in this PR
Adds self-healing daemon artifact recovery and more resilient lifecycle commands.
Changes:
- Detects stale ownership, including Linux PID reuse.
- Makes stop/restart idempotent and adds
stop --force. - Adds tests, documentation, API reports, and change records.
| File | Description |
|---|---|
libraries/rush-client-core/src/ProcessStartTime.ts |
Implements Linux process start-time detection. |
libraries/rush-client-core/src/test/ProcessStartTime.test.ts |
Tests start-time and PID-reuse detection. |
libraries/rush-client-core/src/DaemonOwnership.ts |
Adds artifact inspection, reclaim, and reset APIs. |
libraries/rush-client-core/src/connectOrStartDaemon.ts |
Integrates stale-ownership recovery into startup. |
libraries/rush-client-core/src/test/connectOrStartDaemon.test.ts |
Covers stale and conflicting artifact states. |
libraries/rush-client-core/src/index.ts |
Exports the reset API. |
libraries/rush-client-core/README.md |
Documents recovery behavior. |
common/reviews/api/rush-client-core.api.md |
Records the new public API. |
apps/rush-cli-client/src/daemonCommands.ts |
Implements idempotent lifecycle commands and force reset. |
apps/rush-cli-client/src/test/launchClient.test.ts |
Tests stop, restart, and force-reset behavior. |
apps/rush-cli-client/README.md |
Documents lifecycle command semantics. |
common/changes/@rushstack/rush-client-core/daemon-recovery_2026-09-23.json |
Adds the core package change record. |
common/changes/@rushstack/rush-cli-client/daemon-recovery_2026-09-23.json |
Adds the CLI package change record. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…shutdown, validate records before waiting, reset hint on unresolved handoff Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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-recovery-and-stop-restart
branch
September 24, 2026 19:08
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
Stale or foreign daemon artifacts no longer disable the opt-in Rush daemon for a workspace permanently, and the
rush-client daemonmanagement commands now handle "no daemon running" cleanly.@rushstack/rush-client-core): startup now reclaims leftover artifacts when it can prove they are safe to remove:pid.json;pid.json;pid.jsonwhose PID has been reused by an unrelated process.rush-client daemon stop: exits 0 with{"state":"notRunning"}when no daemon is listening. Before this change it exited 1 with "Could not connect".rush-client daemon restart: starts a daemon when none is running, the same asdaemon start.rush-client daemon stop --force: backed by the newresetDaemonArtifactsAsync()API. It removes this workspace'spid.json,.sockand.startingfiles after checking that no listener is bound and no live owner holds the record. Every fail-closed startup message now points to it.Root cause
assertNoLiveOwner/readDaemonOwnershipfailed closed on all three artifact states.waitForHandoffAsyncalso polled a live but unrelated PID until the 15 s deadline, so each command paid about 16.5 s before falling back to in-process Rush.daemonCommands.tsalways usedDaemonClient.connectAsyncforstopandrestartand let the connection error through.Fix
ProcessStartTime.ts(Linux only): computes a process's wall-clock start time from/proc/<pid>/statfield 22 and/proc/uptime.performance.timeOrigin. If that check fails (unexpectedUSER_HZ, a clock jump, or a non-standard/proc), the start time is treated as unknown.startedAt. When the start time is unknown, behaviour stays fail-closed as before.DaemonOwnership.ts, withreclaimAbandonedOwnershipAsync. It runs only while holding the start mutex with no.startingreservation, so no legitimate daemon can be between binding its socket and writing its record.ECONNREFUSED/ENOENT, which proves nothing is listening.waitForHandoffAsyncandwaitForPreviousDaemonAsyncstop waiting on an owner whose PID is shown to be reused..startingreservations are handled. The only code that removes a.startingfile is the explicit, user-invokedstop --force.Tests
connectOrStartDaemon.test.tsnow covers each artifact state:startedAt→ recovers without waiting out the deadline, and the unrelated process is untouched;--forcehint, andresetDaemonArtifactsAsyncrefuses too;ProcessStartTime.test.ts: start-time estimate, reuse detection, and unknown or invalid inputs.launchClient.test.ts:stopandstop --forcewith no daemon (notRunning, exit 0);restartwith no daemon starts one;stop --forceon the leftovers of a killed daemon (state: "reset",removedPaths).launchClient: 16/16 pass;rush build --to @rushstack/rush-cli-client(including lint) passes.Linux validation
The script combines A05's
s68.shS8a–c ands1.sh. It ran as onelabinvocation on a 12-project synthetic workspace: first with the unfixed toolchainrush-client, then with the fixedapps/rush-cli-client/bin/rush-client. Timings are noisy because the host was heavily loaded..sockwith nopid.jsonpid.jsonpid.jsonnames a live unrelated PIDstop --forceon kill -9 leftovers{"state":"reset","removedPaths":[pid.json, sock]}, exit 0stopwith no daemon{"state":"notRunning"}, exit 0restartwith no daemon{"state":"ready",...}, exit 0;statusthen reports readyOptional follow-ups
ps -o etime=). Today macOS and Windows still fail closed and point tostop --force.daemon statussuggestrush-client daemon startwhen nothing is listening.Fixes #6061
This change came out of the automated rushd Linux behaviour/performance analysis (the "Rushd Hive", board bugs #77 and #78).