[rush] Treat a phased command with zero operations as success - #5915
Merged
Conversation
A phased command whose plugins legitimately produce no operations exited with code 1, even though nothing failed. OperationGraph._scheduleIterationAsync does not schedule an iteration when there are no non-silent operations, so executeAsync early-returns OperationStatus.NoOp without firing any graph hooks. PhasedScriptAction then computed `success = status === OperationStatus.Success`, so NoOp was reported as a failure. This broke @rushstack/rush-buildxl-graph-plugin, which writes the build graph to disk in response to --drop-graph and then returns an empty operation set because there is nothing left to execute. Every BuildXL build using the Rush resolver failed with DX11901/DX11230 even though the graph was produced correctly. Because the early return skips all graph hooks, this could not be worked around by a plugin. PhasedScriptAction already treats an empty project selection as success, so treating an empty operation set as a failure was inconsistent. Adds a regression test: a mock in-repo plugin clears all operations during createOperationsAsync, mirroring what rush-buildxl-graph-plugin does, and the command is expected to succeed without spawning anything.
There was a problem hiding this comment.
Pull request overview
This PR fixes a regression where phased commands that legitimately produce zero operations (e.g., a plugin consumes the work and returns an empty operation set) would exit with a nonzero code due to OperationStatus.NoOp being treated as failure in PhasedScriptAction. It aligns “no operations to execute” with existing behavior where “no projects matched” is considered a successful no-op.
Changes:
- Treat
OperationStatus.NoOpas a success outcome for phased commands and key the completion message off the computedsuccessflag. - Add a regression test that wires an in-repo plugin which clears the operation set and verifies the command succeeds without spawning any tasks.
- Add test fixtures + Heft copy wiring to stage the mock plugin into the fixture autoinstaller (mirroring the existing flush-telemetry mock plugin pattern), plus a patch change file for
@microsoft/rush.
Show a summary per file
| File | Description |
|---|---|
| libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts | Treats NoOp execution status as success and uses the computed success for the final terminal status line. |
| libraries/rush-lib/src/cli/test/RushCommandLineParser.test.ts | Adds regression test ensuring a phased command succeeds when a plugin returns an empty operation set, and asserts no tasks were spawned. |
| libraries/rush-lib/src/cli/test/rush-mock-clear-operations-plugin/rush-plugin-manifest.json | Adds manifest for a mock plugin that clears operations. |
| libraries/rush-lib/src/cli/test/rush-mock-clear-operations-plugin/package.json | Adds package metadata for the mock plugin. |
| libraries/rush-lib/src/cli/test/rush-mock-clear-operations-plugin/index.ts | Implements mock plugin that taps createOperationsAsync at a very late stage and returns an empty Set<Operation>(). |
| libraries/rush-lib/src/cli/test/clearOperationsAndRunBuildActionRepo/rush.json | Adds a new unit-test repo fixture for the regression scenario. |
| libraries/rush-lib/src/cli/test/clearOperationsAndRunBuildActionRepo/common/config/rush/rush-plugins.json | Configures the fixture to load the mock plugin via an autoinstaller. |
| libraries/rush-lib/src/cli/test/clearOperationsAndRunBuildActionRepo/common/autoinstallers/plugins/package.json | Defines the fixture autoinstaller dependency on the mock plugin (file: reference). |
| libraries/rush-lib/src/cli/test/clearOperationsAndRunBuildActionRepo/common/autoinstallers/plugins/rush-plugins/rush-mock-clear-operations-plugin/rush-plugin-manifest.json | Stages the plugin manifest in the fixture’s autoinstaller plugin manifest folder. |
| libraries/rush-lib/src/cli/test/clearOperationsAndRunBuildActionRepo/a/package.json | Adds fixture project “a” with build/rebuild scripts for baseline behavior. |
| libraries/rush-lib/src/cli/test/clearOperationsAndRunBuildActionRepo/b/package.json | Adds fixture project “b” with build/rebuild scripts for baseline behavior. |
| libraries/rush-lib/config/heft.json | Copies the built mock plugin output into the fixture autoinstaller node_modules location (matching the existing mock plugin pattern). |
| common/changes/@microsoft/rush/fix-noop-exit-code_2026-07-28-18-56-00.json | Records the patch change for @microsoft/rush. |
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 0
- Review effort level: Lite
Address review feedback: use a shared SUCCESSFUL_EXECUTION_STATUSES set so that an iteration short-circuited by a tap with a successful bail status is reported as success, alongside the NoOp case. Also drop the unused rushConfiguration parameter from the mock plugin. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bharat Middha (bmiddha)
approved these changes
Aug 5, 2026
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
Fixes #5914.
A phased command whose plugins legitimately produce no operations exits with code 1, even
though nothing failed. This breaks
@rushstack/rush-buildxl-graph-plugin, so every BuildXL buildusing the Rush resolver fails.
Details
OperationGraph._scheduleIterationAsyncdoes not schedule an iteration when there are no non-silentoperations:
so
executeAsyncearly-returnsOperationStatus.NoOpwithout firing any graph hooks:and
PhasedScriptActioncomputed:NoOp !== Success→success = false→AlreadyReportedError→ exit 1.DropBuildGraphPluginwrites the build graph to disk in response to--drop-graphand then returnsnew Set()because there is nothing left for Rush to execute. The result is thatrush <command> --drop-graphexits 1 with no error message and a correctly-written graph file,which BuildXL reports as
DX11901/DX11230.Because the early return skips all graph hooks, this cannot be worked around by a plugin — there
is no hook available to observe or correct the status.
Why this is the right fix
PhasedScriptActionalready treats an empty project selection as success:So "nothing to do because no projects matched" was a success while "nothing to do because no
operations were produced" was a failure. This change makes them agree.
The completion message now keys off
successrather than re-deriving the status, so a no-opinvocation is also reported in green rather than looking like a silent failure.
Changes
PhasedScriptAction.ts— treatOperationStatus.NoOpas success; usesuccessfor thecompletion message. Removes the now-unused
resultlocal.RushCommandLineParser.test.tsplus fixtures:rush-mock-clear-operations-plugin/— an in-repo plugin that tapscreateOperationsAsyncatstage: Number.MAX_SAFE_INTEGERand returns an empty set, mirroringDropBuildGraphPlugin.clearOperationsAndRunBuildActionRepo/— mock repo wiring that plugin.config/heft.json— copy the mock plugin into the fixture's autoinstaller, matching theexisting
rush-mock-flush-telemetry-pluginpattern.@microsoft/rush(patch).Verification
expect(parser.executeAsync()).resolves.toEqual(true)→Received: falserush-libsuiterush build --to @microsoft/rush-libNotes for reviewers
An alternative would be to give phased commands a supported "handled — stop here successfully"
mechanism, analogous to
IGlobalCommand.setHandled(), so plugins likeDropBuildGraphPlugindo nothave to signal completion by returning an empty operation set. That seemed like a larger API change
than warranted for a regression fix, but I'm happy to go that route instead.