Skip to content

[rush-daemon] Print the native operation summary and duration line on the daemon path - #6068

Merged
Sean Larkin (TheLarkInn) merged 4 commits into
mainfrom
thelarkinn-fix-rushd-summary-banner
Sep 24, 2026
Merged

Sean Larkin (TheLarkInn) merged 4 commits into
mainfrom
thelarkinn-fix-rushd-summary-banner

Conversation

@TheLarkInn

@TheLarkInn Sean Larkin (TheLarkInn) commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

On the daemon path (rush-client build), the end-of-run summary tables (==[ SUCCESS / FROM CACHE / SKIPPED / BLOCKED / FAILURE: N operations ]==, including the error or warning report under --[ FAILURE: … ]--) and the trailing rush build (X seconds) line were never printed. A warm no-op build printed 0 bytes. This PR restores them for each request.

Root cause

  • PhasedScriptAction.ts gives the daemon engine new Terminal(new NoOpTerminalProvider()) as presentationTerminal. So OperationResultSummarizerPlugin writes into a no-op terminal. Only eventSink.onActivity lines reach the client.
  • The summarizer runs in afterExecuteIterationAsync, and that hook runs once per shared iteration. Coalesced requests share that iteration, and each may have selected a different subset of operations.
  • A warm no-op never schedules an iteration (scheduleIterationAsync returns false), so nothing ran to print a summary. The client prints nothing for a result.

Fix

  • New libraries/rush-daemon/src/PhasedRequestSummary.ts. When the batch coordinator finishes each participating live request (#finishEntryAsync, before the final flush and the result frame), it:
    • builds that request's own result map. It walks graph.operations (so the order matches native), keeps only the request's activeOperations, and uses the records the request's sink observed. Selected operations that the warm graph disabled or did not schedule are reported as Skipped ("These operations were already up to date:"). It never mutates the shared records.
    • renders the map with rush-lib's own _printOperationStatus, the same code the native summarizer uses, so the output is byte-identical.
    • writes rush <command> (<duration>), or rush <command> - Errors! (…) on an execution error. The duration is measured from when the router receives the request.
    • sends the output to that request's PhasedRequestEventSink.onActivity, buffered into one activity event per contiguous stdout or stderr run. Operations failed. goes to stderr, as in native.
  • @microsoft/rush-lib: export the existing @internal _printOperationStatus (and update the API report).

Why activity events and not a new protocol frame: this follows the approach EngineTerminalProvider already uses. The existing LegacyCollatedRenderer renders global activityChanged text as-is at every verbosity. So the change needs no protocol, transport, client or renderer changes, and it stays compatible with the existing wire contract. A structured requestSummary event could be added later for the reporter renderers, but it isn't needed for parity. Building the summary per request (not per iteration) keeps presentation isolated: coalesced clients each get a summary of only what they asked for.

Tests

New libraries/rush-daemon/src/test/PhasedRequestSummary.test.ts. It drives PhasedRequestRouter against a real OperationGraph:

  • cold build: ==[ SUCCESS: 2 operations ]== + operation names + the rush build (N.NN seconds) line, before the result frame
  • warm no-op (scheduled: false): ==[ SKIPPED: 2 operations ]== / "already up to date" + the duration line
  • failing build: BLOCKED + FAILURE tables, --[ FAILURE: … ]--, Operations failed. on stderr, and the duration line
  • two coalesced requests in one iteration: each summary lists only its own operation

rush build --to @rushstack/rush-daemon --to @rushstack/rush-cli-client (including lint) and rush test --only @rushstack/rush-daemon pass on Linux, and every existing rush-daemon test still passes.

Linux validation

WSL Ubuntu-24.04, node 22.23.2, synthetic 12-project workspace (mkws-synth --projects 12), RUSH_DAEMON=1, one shell per sequence: cold build, then a warm no-op, then touch packages/p04/src/FAIL + build. "Before" is the unfixed rush-client from main; "after" is apps/rush-cli-client/bin/rush-client built from this branch.

scenario before after
cold build exit 0, 37 lines / 1622 B. Last line: "p12 (build)" completed successfully in 0.53 seconds. exit 0, 58 lines / 2137 B. Ends with ==[ FROM CACHE: 12 operations ]== table + rush build (1.93 seconds)
warm no-op exit 0, 0 bytes exit 0, 322 B: ==[ SKIPPED: 12 operations ]== / "These operations were already up to date:" + rush build (0.39 seconds)
failing build (p04) exit 1, 29 lines. Last line: "p12 (build)" is blocked by "p04 (build)". exit 1, 61 lines: SKIPPED(3) / BLOCKED(8) / FAILURE(1) tables, the TS2322 error under --[ FAILURE: p04 (build) ]--, Operations failed., rush build (0.70 seconds)
After: warm no-op (complete output)



==[ SKIPPED: 12 operations ]===================================================

These operations were already up to date:
  p01 (build)
  ...
  p12 (build)


rush build (0.39 seconds)
After: failing build (tail)
==[ BLOCKED: 8 operations ]====================================================

These operations were blocked by dependencies that failed:
  p05 (build)
  ...
==[ FAILURE: 1 operation ]=====================================================

--[ FAILURE: p04 (build) ]-----------------------------------[ 0.13 seconds ]--

src/index.ts(3,7): error TS2322: Type 'string' is not assignable to type 'number'. [p04]


Operations failed.

rush build (0.70 seconds)

Fixes #6053

This PR came out of the automated rushd Linux performance/behavior analysis ("Rushd Hive").

…r phased requests

Fixes #6053

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Summary rendering does not honor the request-local warnings policy, causing output to diverge from the command result and native Rush.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Restores native operation summaries and duration output for daemon-routed phased builds, including warm no-op and coalesced requests.

Changes:

  • Adds request-scoped summary rendering using Rush’s native formatter.
  • Emits summaries before each request result.
  • Adds coverage for cold, warm, failing, and coalesced builds.
File Description
libraries/​rush-sdk/​src/​test/​__snapshots__/​script.test.ts.snap Updates exported API snapshot.
libraries/​rush-lib/​src/​index.ts Exports the internal summary formatter.
libraries/​rush-daemon/​src/​test/​PhasedRequestSummary.test.ts Tests request summary behavior.
libraries/​rush-daemon/​src/​PhasedRequestSummary.ts Implements request-scoped summary rendering.
libraries/​rush-daemon/​src/​PhasedRequestRouter.ts Emits summaries before result frames.
common/​reviews/​api/​rush-lib.api.md Records the new internal export.
common/​changes/​@rushstack/​rush-daemon/​rushd-summary-banner_2026-09-24-01-30.json Adds daemon patch changelog.
common/​changes/​@microsoft/​rush/​rushd-summary-banner_2026-09-24-01-30.json Adds Rush patch changelog.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread libraries/rush-daemon/src/PhasedRequestSummary.ts
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Closed

Development

Successfully merging this pull request may close these issues.

[rush] rushd: daemon-path builds never print the operation summary or "rush build (N seconds)" line, and a warm no-op build prints nothing at all

3 participants