Skip to content

fix(parse): let a bundle contain a supplied short - #1175

Merged
jdx merged 1 commit into
feat/lint-examples-parsefrom
fix/supplied-short-in-a-bundle
Aug 21, 2026
Merged

jdx merged 1 commit into
feat/lint-examples-parsefrom
fix/supplied-short-in-a-bundle

Conversation

@jdx

@jdx jdx commented Aug 21, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to the -vh question raised in review on #1168. Stacked on that branch, because the -V half needs the supplied version flag that lands there; the diff here is one commit.

The bug

-vh was refused as an unknown word wherever -h was not declared:

$ demo -vh
unexpected word: -vh

short_bundle_is_known asks whether every letter names a declared flag, on the grammar's rule that a token containing an unrecognized letter is not a bundle at all. But -h is recognized — the parser supplies it, as it supplies -V on a root that declares a version — so the token was a bundle and the check was reading it against the wrong set. The peeling machinery below it was always fine: -v applies and pushes -h back, which the whole-token spelling then answers. It simply never got there.

Who else was already right

-vh
usage-argv help
usage-go help
clap help
usage-lib unexpected word: -vh

Both other implementations resolve the letter through the same lookup that finds a declared short (find_short in argv/src/lib.rs, findShort in go/argv/parser.go, each falling back to a supplied HELP_SHORT/VERSION_SHORT). usage-lib is the implementation the corpus measures the others against, and it was the one that disagreed — the same shape as the --version divergence in the commit below it, and unseen for the same reason: the binding corpus has no vocabulary for an invocation that prints and exits, so nothing measures these at all.

What changed

  • short_bundle_is_known counts a supplied -h or -V as a recognized letter, under exactly the conditions is_help_arg and is_version_arg already state — asked one letter at a time, because a bundle is read one letter at a time.
  • The letter is answered wherever it sits, so -hv asks for help as surely as -vh does. Neither reached the whole-token spellings that handled -h alone.
  • Always the short response: -h is short help however many letters share its token, and -V the concise version. The long forms belong to the long spellings.
  • -? stays a whole-token spelling rather than a letter anyone bundles, and -V keeps its root-only rule — demo run -vV is still refused.

Two rules deliberately unweakened, both tested:

  • A declared letter keeps its meaning. Nothing is supplied where the CLI spent the letter, so a -h meaning --host still reads -vhlocal as a bundle and its value.
  • A letter nothing supplies still refuses the whole bundle. -az sets nothing on the way to discovering that z names nothing, which is what the corpus pins in short-bundle-unknown-letter-applies-nothing.

disable_help_flag takes the letter back out, and that is tested too.

The grammar

docs/spec/argv.md said nothing about supplied letters anywhere — which is what left three implementations agreeing by coincidence and one disagreeing without anything noticing. The section that states the bundle rule now states this one beside it.

Observed, not fixed

While testing value-taking shorts: -jh on a spec declaring -j <n> errors with Invalid flag --jobs: requires an argument in usage-lib, while usage-argv binds jobs = "h" — which is what the grammar's own table says (-j8 takes the rest of the token). Present on main, unchanged by this PR, and a different mechanism: the pushed-back remainder is refused as a pending value for looking flag-like. Worth its own change.

Testing

Six cases in lib/src/parse.rs covering both letters, both orders, the short-page rule, the root-only rule for -V, a declared letter winning, disable_help_flag, and the unchanged -az. cargo test --all --all-features passes (1,990), clippy and prettier clean.

🤖 Generated with Claude Code


Note

Medium Risk
Changes argv binding for help/version shorts, which is user-facing CLI behavior, but the rules are narrow and covered by tests.

Overview
Treats parser-supplied -h and -V as recognized short letters so tokens like -vh and -hv are real bundles that print help or version, matching usage-argv, usage-go, and clap.

short_bundle_is_known now counts those letters under the same conditions as is_help_arg / is_version_arg. The letter is answered wherever it sits, always as the short help page or concise version. A declared -h still wins (-vhlocal as --host), unknown letters still refuse the whole token (-az), and disable_help_flag / root-only -V stay as they were.

The argv grammar now states this beside the bundle rule.

Reviewed by Cursor Bugbot for commit 13b278a. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ef2e530-09a0-450a-984e-e40fd797b3f9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Instruction counts

benchmark trend instructions Δ wall (min) Δ
markdown ▁█▇▇▇ 226,037,024 → 226,092,214 +0.02% 22.47 → 26.30ms +17.02%
startup ▄▆▆▁█ 1,223,854 → 1,225,502 +0.13% 1.47 → 1.67ms +13.50%

No instruction-count regression above 1%.

Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run.

Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.

Shadow comparison

Parsing mise use -g node@20 against a shadow of mise's committed spec.
Reported, not gated: the shadow grows as the derive learns to express more, so
what to watch is the ratio rather than either column.

framework instructions, cold parse vs usage
usage 8315 —
argh 6307 0.8x
clap 6316290 759x
bpaf 21909019 2634x
                                              min       p01       p10    median
usage-rs: argv -> struct                      433       439       452       463  ns
argh: argv -> struct                          275       279       287       299  ns
clap: build tree + parse -> struct         523973    525356    531971    551821  ns
bpaf: build parser + parse -> struct      1599038   1599038   1629376   1676681  ns

usage: argv -> struct                             434 ns      0.43 µs
clap: build tree + parse -> struct             544009 ns    544.01 µs
clap: parse -> struct, tree reused              23106 ns     23.11 µs
clap: build tree only                          334804 ns    334.80 µs

e649dbfdde39 vs 2e6fe2bbf987 · measured on the runner, not pushed to the history.

@jdx
jdx force-pushed the fix/supplied-short-in-a-bundle branch from e649dbf to a1fb474 Compare August 21, 2026 13:55

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a1fb474. Configure here.

Comment thread lib/src/parse.rs
let is_bundle =
word.starts_with("--") || short_bundle_is_known(&out.available_flags, &word);
let is_bundle = word.starts_with("--")
|| short_bundle_is_known(spec, &out.cmds, &out.available_flags, &word);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bundled help/version skips past subcommands

High Severity

Phase 1 now treats a short bundle containing a supplied -h or -V as a known flag and keeps scanning for subcommands. A following subcommand is selected before Phase 2 peels the token, so ex -vh run answers with run's help instead of the root's, and ex -vV run fails the root-only version check and surfaces a stray word instead of the version. Whole-token -h/-V and first-letter forms like -hv/-Vv still stop the scan, so the same argv disagrees with itself and with one-pass parsers.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a1fb474. Configure here.

@jdx
jdx force-pushed the fix/supplied-short-in-a-bundle branch from a1fb474 to 0c02861 Compare August 21, 2026 14:02
`-vh` was refused as an unknown word wherever `-h` was not declared, because
`short_bundle_is_known` asked only the declared flags and a token holding an
unrecognized letter is not a bundle at all. But `-h` *is* recognized — the
parser supplies it, as it supplies `-V` on a root that declares a version — so
the token was a bundle and the rule was reading it against the wrong set.

usage-argv and usage-go both resolve the letter through the same lookup that
finds a declared short, and clap prints help for `-vh` too. usage-lib is the
implementation the corpus measures the others against, and it was the one that
disagreed — the same shape as the `--version` divergence in the commit below,
and unseen for the same reason: the binding corpus has no vocabulary for an
invocation that prints and exits, so nothing measures these.

The letter is answered wherever it sits, so `-hv` asks for help as surely as
`-vh` does; neither reached the whole-token spellings that handled `-h` alone.
Always the short response — `-h` is short help however many letters share its
token, and `-V` the concise version — with the long forms left to the long
spellings. `-?` stays a whole-token spelling rather than a letter, and `-V`
keeps its root-only rule.

A spec that declares the letter keeps it: nothing is supplied where the CLI
spent it, so a `-h` meaning `--host` still reads `-vhlocal` as its own. And a
letter nothing supplies still refuses the whole bundle, which is the rule this
must not weaken: `-az` sets nothing.

The grammar now says so, in the section that states the bundle rule. It said
nothing about supplied letters at all, which is what left three implementations
agreeing by coincidence and one disagreeing without anything noticing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jdx
jdx force-pushed the fix/supplied-short-in-a-bundle branch from 0c02861 to 13b278a Compare August 21, 2026 14:23
@jdx
jdx merged commit cc69871 into main Aug 21, 2026
8 of 9 checks passed
@jdx
jdx deleted the fix/supplied-short-in-a-bundle branch August 21, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant