fix(cli): nested dispatch resolution and chain failure reporting - #92
Conversation
Flags after the task reach the task, at every level. `args` is trailing_var_arg, but clap only collects raw values once it holds one, so `run tsc -p tsconfig.json --noEmit` bound `-p` to `--parallel` and rejected `--noEmit`. Insert the delimiter before parsing. Closes #89. Nested runners carry an invocation stack; a task already on it is refused with the cycle it closes. Closes #90. Installed dependencies resolve to the binary their manifest declares, so an npm alias works at all (its directory name is in no registry, npx 404s). Ambiguous and zero-bin packages are reported, not guessed. Closes #91. `--quiet` no longer writes ::group:: to stdout. Workflow commands are a stdout protocol, so `run -q` inside `npm pack --json` corrupted the JSON. Markers cannot move to stderr: GHA does not preserve inter-stream order. Closes #86. Multi-task chains close with a per-task roll-up plus GHA error annotations, and attribute the aggregate exit code. Closes #87. Also: dispatch arrow names the exec primitive it runs (npx/bunx/pnpm exec), not the package manager.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (1)**/CHANGELOG.md📄 CodeRabbit inference engine (Custom checks)
Files:
🧠 Learnings (5)📚 Learning: 2026-03-26T20:05:44.851ZApplied to files:
📚 Learning: 2026-04-21T15:16:40.277ZApplied to files:
📚 Learning: 2026-05-04T23:28:17.947ZApplied to files:
📚 Learning: 2026-06-01T17:42:48.461ZApplied to files:
📚 Learning: 2026-06-11T18:52:28.233ZApplied to files:
🪛 LanguageToolREADME.md[uncategorized] ~213-~213: The official name of this software platform is spelled with a capital “H”. (GITHUB) [uncategorized] ~215-~215: Possible missing comma found. I...
|
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Semver Version Bump Validation | Source files changed, but Cargo.toml stayed at 0.20.0 on both base and HEAD, so no required SemVer bump was made. | Bump the crate version in Cargo.toml (and any other version file) to a higher SemVer, e.g. 0.21.0 for these backward-compatible changes. |
✅ Passed checks (7 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title is descriptive, uses a conventional fix: prefix, and matches the nested dispatch and chain failure work. |
| Description check | ✅ Passed | The description clearly relates to the CLI fixes and linked issues, so it is on topic. |
| Linked Issues check | ✅ Passed | The implementation and tests cover #86, #87, #89, #90, and #91 with the expected behaviours. |
| Out of Scope Changes check | ✅ Passed | The changes shown are documentation, schema, tests, and code directly tied to the linked CLI fixes. |
| Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 30.00%. |
| Changelog Update | ✅ Passed | PASS: 10 src files changed, and CHANGELOG.md updates ## [Unreleased] with Added/Fixed entries; no version bump/new version header is present. |
| Agents.Md Documentation Updated | ✅ Passed | No AGENTS.md files exist in the repository, so there is nothing to update for these CLI/workflow changes. |
📋 Issue Planner
Built with CodeRabbit's Coding Plans for faster development and fewer bugs.
View plan used: #91
Comment @coderabbitai help to get the list of available commands.
Review nit.
`[github].group_output` gates only the annotations. The roll-up follows `--quiet`/`--no-warnings` alone, so opting out of Actions decoration keeps it. README and CHANGELOG claimed both followed group_output. Pin all three gates with integration tests; the annotation paths had none.
Closes #86, closes #87, closes #89, closes #90, closes #91.
Five open
area: clibugs that all sit on the same two seams: how a tokenresolves when runner re-enters itself, and what a chain reports when it fails.
Nested dispatch
#89, flags after the task were reparsed as runner's own.
argsisdeclared
trailing_var_arg, but clap only collects raw values once thatpositional already holds one. A flag written immediately after the task was
still matched against runner's options, so
run tsc -p tsconfig.json --noEmitbound
-pto--parallel, entered chain mode, and rejected--noEmitas anon-task positional. No number of
--delimiters survived a nested dispatch.The surrounding code already documented the intended rule in three separate
comments ("chain flags precede task names; everything after the task belongs
to the task"). Nothing enforced it.
forward_args_after_taskinserts thedelimiter before parsing, so clap enforces the documented rule at every level.
#90, a task could resolve back to itself.
"tsc": "run -q tsc"spawnedcopies of itself until the process tree collapsed, and
--quietonly hid theevidence. Nested runners now carry an invocation stack in
RUNNER_TASK_STACK,inherited through the package manager sitting between two runner processes. A
task already on the stack is refused with the cycle it closes:
Frames are keyed on canonical root + source + name, so a workspace member
running its own
buildfrom the root'sbuildstays a fan-out.--quietdoes not suppress the diagnostic.
#91, installed dependencies went to the registry. A bare token went
straight to
npx, which resolves against npm. That cannot work for an npmalias:
"@typescript/native": "npm:typescript@^7"installs into a directorywhose name exists in no registry, so the fallback 404'd. Runner now reads
node_modules/<token>/package.jsonand runs the binary it declares, withoutnetwork access:
Packages declaring several binaries none of which is named after the package,
or none at all, are reported instead of guessed at.
Chain reporting
#87, a failed chain gave no consolidated signal. Multi-task chains now
close with a roll-up on stderr, and the aggregate exit code says where it came
from:
Fail-fast runs name the tasks they never started. Under Actions each failure
also becomes an
::error::annotation. Single-task chains get nothing, sincethe per-task timing line already says it.
#86,
--quietstill wrote::group::to stdout. Workflow commands are astdout protocol, so
run -q <task>inside a pipeline whose stdout a parentparses (
npm pack --json) corrupted that output.Moving the markers to stderr was the tempting fix and is wrong: GitHub Actions
does not preserve relative order between the two streams, so a fold opened
there closes around whatever lines land between. Suppression under
--quietis the correct fix. That reasoning is recorded on
emits_groupso it is notre-derived later.
--quietnow suppresses every piece of runner's own output,including the parallel block headers, which reach stdout by the same route.
Also
The dispatch arrow names the exec primitive it actually runs (
npx,bunx,pnpm exec,yarn exec) rather than the package manager.→ npm typescript@7read as though runner had run
npm <package>as a shell command, which is what#89 reported as a second defect.
Verification
959 unit tests and 51 integration tests, no failures.
dprint check,cargo lint,cargo testall clean, the three gatesrelease.ymlruns.Schemas regenerated.
New coverage in
tests/nested_dispatch.rs: eight tests driving real processtrees for forwarding, cycles, and dependency resolution. Chain summary tests in
tests/chain_integration.rs; a GitHub Actions stdout test intests/quiet_dispatch.rs.Each issue was reproduced before the change and re-run after.
Behaviour changes worth a second look
--quietnow also drops the plain (non-Actions) parallel block headers.They reach stdout too, so the same argument applies.
node_modules, notonly declared dependencies. A transitive package now resolves locally where
it previously went to
npx.