Builtin verbs always builtin on runner, task-or-builtin on run#55
Conversation
…ltin on `run` `runner <verb>` (install/clean/list/info/completions) always runs the builtin; never shadowed by a same-named task. The run path (`run <verb>` / `runner run <verb>`) runs a same-named task when one exists, else falls back to the builtin's default form instead of PM-exec — so `run install` with no `install` task installs deps, while a `Makefile` `install` target is reached via `run install`. Inverts the old precedence where `runner install` deferred to the task. Bump 0.14.0 (Cargo, AUR PKGBUILDs, doctor example). CHANGELOG stays Unreleased.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
runner | 00426c2 | Commit Preview URL Branch Preview URL |
Jun 22 2026, 12:09 AM |
📝 WalkthroughChange the dispatch precedence for built-in verbs across command surfaces to eliminate ambiguity when projects define tasks with the same names as built-in commands. The explicit The The Updates include:
WalkthroughVersion 0.14.0 inverts and clarifies the dispatch precedence between built-in verbs and project tasks. Explicit Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 6 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (6 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 14-28: The CHANGELOG.md file is missing a required version section
header for the 0.14.0 release. Create a new section header `## [0.14.0] -
YYYY-MM-DD` (replacing YYYY-MM-DD with the actual release date) above the
current entries, then move all the changelog entries from under `##
[Unreleased]` to be placed under this new 0.14.0 section header. Keep the `##
[Unreleased]` section after the 0.14.0 section for future unreleased changes.
This follows the standard changelog format where released versions have their
own dated sections separate from unreleased changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b78c2515-1c9e-498f-88eb-bf171eb0120b
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
CHANGELOG.mdCargo.tomlREADME.mdaur/runner-run-bin/PKGBUILDaur/runner-run/PKGBUILDschemas/doctor.v3.example.jsonsrc/lib.rstests/builtin_verb_dispatch.rstests/fixtures/list-shadowed/justfiletests/info_deprecation.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/CHANGELOG.md
📄 CodeRabbit inference engine (Custom checks)
**/CHANGELOG.md: If any source code files (excluding tests, docs, CI, markdown, or comments-only changes) are modified, CHANGELOG.md MUST also be modified in the same PR.
If a version bump is detected, CHANGELOG.md MUST contain a new section header matching the exact new version number in the format: '## [X.Y.Z] - YYYY-MM-DD'.
If NO version bump is detected, the changes in the PR MUST be added under the existing '## [Unreleased]' section in CHANGELOG.md. The entry MUST describe the changes (e.g., Added, Changed, Fixed, Removed).
Files:
CHANGELOG.md
@(package.json|pyproject.toml|setup.py|Cargo.toml|go.mod|pom.xml|build.gradle|VERSION)
📄 CodeRabbit inference engine (Custom checks)
@(package.json|pyproject.toml|setup.py|Cargo.toml|go.mod|pom.xml|build.gradle|VERSION): If any source code files (excluding tests, docs, CI, markdown, or comments-only changes) are modified, then a version field MUST be updated in one of the following files if present in the repo: package.json, pyproject.toml, setup.py, Cargo.toml, go.mod, pom.xml, build.gradle, or a VERSION file.
The new version MUST follow SemVer (MAJOR.MINOR.PATCH). If the PR introduces breaking changes (removal or renaming of public APIs, changes to function signatures, deleted exported symbols, or incompatible config changes), MAJOR must increment. If it adds backward-compatible functionality, MINOR must increment. If it only fixes bugs without changing public APIs, PATCH must increment.
Files:
Cargo.toml
🧠 Learnings (7)
📚 Learning: 2026-03-26T20:05:44.851Z
Learnt from: kjanat
Repo: kjanat/runner PR: 1
File: src/cmd/mod.rs:67-75
Timestamp: 2026-03-26T20:05:44.851Z
Learning: In Rust, `std::process::Command` does not provide getters for stdio configuration, so whether `Stdio::inherit()` (or other `Stdio::*` settings) was applied cannot be asserted via pure unit tests without spawning a process and inspecting OS-level fds. When reviewing Rust code, do not flag “missing unit test coverage” for stdio configuration on `std::process::Command` as a code issue—treat the explicit `Command::stdin/stdout/stderr` setter calls in the source as the meaningful guarantee.
Applied to files:
tests/builtin_verb_dispatch.rstests/info_deprecation.rssrc/lib.rs
📚 Learning: 2026-04-21T15:16:40.277Z
Learnt from: kjanat
Repo: kjanat/runner PR: 4
File: src/tool/just.rs:132-133
Timestamp: 2026-04-21T15:16:40.277Z
Learning: In Rust, if a method like `ExtractedTask::name() -> &str` returns a borrowed `&str` tied to `&self`, then using `sort_unstable_by_key(|t| t.name())` should be avoided because `by_key` requires the key to not borrow from the element being sorted (it will fail to compile due to the key’s lifetime). Do not recommend `sort_unstable_by_key` as a simplification in this situation. If you need an allocation-free and idiomatic comparison, use `sort_unstable_by(|a, b| a.name().cmp(b.name()))` instead. Using `.to_owned()` inside `by_key` is an alternative but allocates a `String` per element.
Applied to files:
tests/builtin_verb_dispatch.rstests/info_deprecation.rssrc/lib.rs
📚 Learning: 2026-05-04T23:28:17.947Z
Learnt from: kjanat
Repo: kjanat/runner PR: 5
File: src/lib.rs:222-223
Timestamp: 2026-05-04T23:28:17.947Z
Learning: When reviewing Rust `rustdoc` comments (`/// ...`), treat a trailing backslash (`\`) at the end of a comment line as valid CommonMark syntax for a hard line break (rendered as `<br>`). Do not flag such trailing backslashes as papercuts or recommend removing them unless there is clear evidence they are unintended (e.g., they are not in the `rustdoc` comment context or the surrounding formatting contradicts the intended CommonMark hard-break usage).
Applied to files:
tests/builtin_verb_dispatch.rstests/info_deprecation.rssrc/lib.rs
📚 Learning: 2026-06-01T17:42:48.461Z
Learnt from: kjanat
Repo: kjanat/runner PR: 34
File: Cargo.toml:61-61
Timestamp: 2026-06-01T17:42:48.461Z
Learning: In this repo, do not flag `actions_rs::log::GroupGuard` or `actions_rs::log::group_guard` as missing/non-existent when they are referenced in Rust code, because the `actions-rs` crate (dependency `actions-rs = "0.1"` / published v0.1.x) exports `pub struct GroupGuard` and `pub fn group_guard` from `actions_rs::log` (and also exports `actions_rs::env::is_github_actions`). This avoids false positives from outdated/incorrect web search results. If the repo does not depend on `actions-rs = "0.1"` in `Cargo.toml`, then normal missing-import/item checks can apply.
Applied to files:
tests/builtin_verb_dispatch.rstests/info_deprecation.rssrc/lib.rs
📚 Learning: 2026-06-11T18:52:28.233Z
Learnt from: kjanat
Repo: kjanat/runner PR: 45
File: src/lib.rs:659-661
Timestamp: 2026-06-11T18:52:28.233Z
Learning: In Rust, it’s acceptable to use `matches!(cli.command, Some(cli::Command::Doctor { .. }))` (or similar) when the scrutinee comes from a field accessed through a shared reference (e.g., `cli: &Cli`). If the match pattern uses `..` and binds zero fields (so only the enum discriminant is matched), Rust match ergonomics will avoid moving out of the referenced value. Reviewers should not flag such code as a compile-blocking move; confirm with `cargo check` rather than forcing changes like `.as_ref()` when compilation succeeds.
Applied to files:
tests/builtin_verb_dispatch.rstests/info_deprecation.rssrc/lib.rs
📚 Learning: 2026-03-26T16:14:15.754Z
Learnt from: kjanat
Repo: kjanat/runner PR: 1
File: src/tool/go_task.rs:64-81
Timestamp: 2026-03-26T16:14:15.754Z
Learning: If code uses let-chains (e.g., `if let Some(x) = foo && ... && let Some(y) = bar`), ensure the crate’s `Cargo.toml` sets `package.edition = "2024"`. Rust 2021/earlier should not be used with let-chains; require 2024 specifically for compilation.
Applied to files:
Cargo.toml
📚 Learning: 2026-05-15T01:31:48.037Z
Learnt from: kjanat
Repo: kjanat/runner PR: 27
File: src/types.rs:584-585
Timestamp: 2026-05-15T01:31:48.037Z
Learning: In the `kjanat/runner` Rust codebase, remember that `just` treats its `justfile` name as officially case-insensitive and allows the hidden variant: `justfile`, `Justfile`, `JUSTFILE`, etc., and `.justfile`. `TaskSource::Justfile` detection should recognize these filename variants. Also note that `from_label` in `src/types.rs` is for parsing user-supplied qualifier prefixes (e.g. `justfile:build`), not for detecting the on-disk justfile. If you add/adjust qualifier label emission for `TaskSource::Justfile`, it’s not strictly required for backward compatibility because `TaskSource::label()` previously only emitted lowercase `"justfile"`, but adding additional casing/hidden variants as a defensive UX improvement is consistent with `just`’s official support.
Applied to files:
src/lib.rs
🪛 GitHub Actions: PR `#55` / 0_Analyze (rust).txt
tests/builtin_verb_dispatch.rs
[warning] 20-28: Macro expansion failed for 'env'.
[warning] 43-99: Macro expansion failed for '$crate::format_args_nl'.
[warning] 52-126: Macro expansion failed for 'assert' (multiple occurrences).
[warning] 74-112: Macro expansion failed for 'vec'.
tests/info_deprecation.rs
[warning] 17-25: Macro expansion failed for 'env'.
[warning] 40-264: Macro expansion failed for '$crate::format_args_nl' (multiple occurrences).
[warning] 52-264: Macro expansion failed for 'assert' (multiple occurrences).
[warning] 217-221: Macro expansion failed for 'vec'.
🪛 GitHub Actions: PR `#55` / Analyze (rust)
tests/builtin_verb_dispatch.rs
[warning] 20-20: Macro expansion failed for 'env'
tests/info_deprecation.rs
[warning] 17-17: Macro expansion failed for 'env'
🔍 Remote MCP GitHub Grep
Based on the information I've gathered, let me provide a comprehensive summary of the PR context and potential areas for review:
Summary of Relevant Context Found
Code Organization & Testing Patterns
The searches reveal common patterns in Rust CLI projects for integration testing:
-
Integration Test Structure: Rust projects commonly use
std::process::Commandto invoke CLI binaries with.args()and.stdin(Stdio::null())to avoid hanging. Tests verify both output and exit codes. -
Fixture Organization: Standard practice places test fixtures in a
tests/fixtures/directory with subdirectories for different test scenarios (liketests/fixtures/list-shadowed/in your PR). -
Test Naming Conventions: Integration tests follow patterns like:
tests/<feature>_<aspect>.rsfiles- Test functions describing specific behaviors (e.g.,
runner_info_is_the_deprecated_alias_even_when_a_task_is_named_info) - Conditional skipping with
#[test]when dependencies aren't available
Key Observations About Your PR's Testing Approach
Your PR's test implementation (tests/builtin_verb_dispatch.rs and updated tests/info_deprecation.rs) follows these conventions:
- Fixture-based testing: Uses
justexecutable with predefined recipe files to test dispatch behavior - Conditional execution: Tests skip gracefully if
justis not available (best practice for portability) - Behavior verification: Tests assert specific output markers rather than relying on exit codes alone, which is more robust for semantic verification
- Clear naming: Test names explicitly describe what dispatch behavior they verify (explicit
runner <verb>vs.run <verb>paths)
Code Pattern Consistency
The dispatch logic changes in src/lib.rs show:
- Explicit match arms for builtin verbs with clear routing rules
- Removal of conditional task shadowing checks (
has_task(&ctx, "info")) in favor of unconditional builtin routing - Fallback logic that attempts builtin behavior when no task exists
This pattern is conventional for CLI tools with hierarchical command routing.
Documentation Integration
The PR updates documentation (README.md, CHANGELOG.md) with explicit behavior matrices showing:
- Clear precedence rules for
runner <verb>vsrun <verb>invocation patterns - Special case handling for deprecated
runner infocommand - Distinction between qualified lookups (e.g.,
runner justfile:install) which bypass builtin verb routing
🔇 Additional comments (9)
README.md (1)
303-311: LGTM!Cargo.toml (1)
3-3: LGTM!aur/runner-run-bin/PKGBUILD (1)
8-8: LGTM!aur/runner-run/PKGBUILD (1)
8-8: LGTM!schemas/doctor.v3.example.json (1)
26-26: LGTM!src/lib.rs (1)
49-56: LGTM!Also applies to: 214-219, 639-645, 647-691, 818-881
tests/fixtures/list-shadowed/justfile (1)
1-5: LGTM!tests/builtin_verb_dispatch.rs (1)
1-131: LGTM!tests/info_deprecation.rs (1)
4-6: LGTM!Also applies to: 20-23, 171-243
| - Built-in verb dispatch is split between the two surfaces. The explicit | ||
| `runner <verb>` subcommand — `install`, `clean`, `list`, `info`, | ||
| `completions` — is now **always** the built-in and is never shadowed by a | ||
| same-named project task. The run path (`run <verb>` / `runner run <verb>`) | ||
| runs a same-named task when one exists, and otherwise falls back to that | ||
| built-in's default form instead of the package-manager exec path (so | ||
| `run install` with no `install` task installs dependencies rather than | ||
| attempting `bunx install`). Previously the precedence was reversed: | ||
| `runner install` deferred to a task named `install` (e.g. a `Makefile` | ||
| `install` target), which surprised projects whose `install` means "install | ||
| the built artifact" rather than "install dependencies". Reach a same-named | ||
| task with `run install` / `runner run install`; the built-in default for | ||
| `info` on the run path is a plain task list (no deprecation warning, which | ||
| remains specific to the explicit `runner info` subcommand). See | ||
| https://github.com/kjanat/runner/pull/55 |
There was a problem hiding this comment.
Ahoy! Ye be needin' a new version section header in th' CHANGELOG, ye scallywag!
Accordin' to th' coding guidelines, when a version bump be detected (and 'tis detected, from 0.13.1 to 0.14.0 in Cargo.toml), th' CHANGELOG.md MUST contain a new section header in th' exact format: ## [X.Y.Z] - YYYY-MM-DD. Right now, all th' changes be sittin' under ## [Unreleased], which be a grievous violation of th' code of th' seas—er, guidelines.
Ye need to create a new section ## [0.14.0] - YYYY-MM-DD (with th' actual release date) and move these entries there. After that, keep th' ## [Unreleased] section for future changes.
⚓ Proposed fix to add proper version header
## [Unreleased]
+## [0.14.0] - 2026-06-22
+
### Changed
- Built-in verb dispatch is split between the two surfaces. The explicit
`runner <verb>` subcommand — `install`, `clean`, `list`, `info`,
`completions` — is now **always** the built-in and is never shadowed by a
same-named project task. The run path (`run <verb>` / `runner run <verb>`)
runs a same-named task when one exists, and otherwise falls back to that
built-in's default form instead of the package-manager exec path (so
`run install` with no `install` task installs dependencies rather than
attempting `bunx install`). Previously the precedence was reversed:
`runner install` deferred to a task named `install` (e.g. a `Makefile`
`install` target), which surprised projects whose `install` means "install
the built artifact" rather than "install dependencies". Reach a same-named
task with `run install` / `runner run install`; the built-in default for
`info` on the run path is a plain task list (no deprecation warning, which
remains specific to the explicit `runner info` subcommand). See
https://github.com/kjanat/runner/pull/55
- The `run` alias now forwards `--help`/`-h` and `--version`/`-V` to the
task when they follow a task name: `run <task> --help` reaches the
task's own help instead of printing `run`'s (previously `run <task> --`
was required). `run --help`/`--version` with no task — including after
global flags like `run --pm npm --help` — still print this binary's own
help/version, and `run <task> -- --help` still forwards literally. The
`runner run` subcommand is unchanged. Because `-h`/`--help`/`-V`/
`--version` are no longer clap arguments on the alias, they are
documented in the help footer rather than the options list.
+
+## [Unreleased]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` around lines 14 - 28, The CHANGELOG.md file is missing a
required version section header for the 0.14.0 release. Create a new section
header `## [0.14.0] - YYYY-MM-DD` (replacing YYYY-MM-DD with the actual release
date) above the current entries, then move all the changelog entries from under
`## [Unreleased]` to be placed under this new 0.14.0 section header. Keep the
`## [Unreleased]` section after the 0.14.0 section for future unreleased
changes. This follows the standard changelog format where released versions have
their own dated sections separate from unreleased changes.
Source: Coding guidelines
What
Splits builtin-verb dispatch across the two surfaces for
install,clean,list,info,completions:runner <verb>(explicit subcommand) → always the builtin. Never shadowed by a same-named task.run <verb>/runner run <verb>→ run a same-named task when one exists, else fall back to the builtin's default form (instead of the package-manager exec path).Why
The precedence was reversed:
runner installdeferred to a task namedinstall(e.g. aMakefileinstalltarget), so in a repo whoseinstallmeans "install the built artifact to/usr/local",runner installranmake installand hit a permission error instead of installing dependencies. Meanwhilerun installwith noinstalltask hitbunx installrather than the builtin dep-install.Now the two verbs are cleanly separated:
runner installinstalls deps;run installreaches themake installtask.Behavior matrix
runner installrun install/runner run installrun install/runner run installrun install <args>run justfile:installrunner infoNotes
infoon the run path maps to a plainlist(no deprecation warning — that stays specific to the explicitrunner infosubcommand).source:verbtokens carry the prefix, so they never match a bare verb arm and fall through untouched.runner completionsin CI with$SHELLunset now always tries the builtin (errors) rather than running a same-namedcompletionstask; reach the task viarun completions.Tests
runner infostays the deprecated alias even with aninfotask;run info/runner run inforun the task).tests/builtin_verb_dispatch.rs+list-shadowedfixture exercising the verb-agnostic helper via the cheaplistbuiltin (always-builtin onrunner, task-claims onrun, builtin-fallback onrunwhen no task) — avoids spawning real package managers.tool::miseunit test (fails identically onmaster).Bumps
0.14.0(Cargo + lock + both AUR PKGBUILDs + doctor example). CHANGELOG entry under[Unreleased].