--help on a non-first subcommand shows the first subcommand's help instead
Nobody has claimed this yet.
- Dominant language
- Scala
- Stars
- 138
- Forks
- 91
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 13
Description
Summary
root <subcommand> --help renders the first registered subcommand's help instead of the one the user named. This affects every non-first subcommand, and nested cases (root parent child --help). Root-level root --help is correct.
Related to #448 / #576: those fixed the bare --help case (no subcommand named, which dived into the first subcommand) and the cmd --help payload leftover case. They did not cover an explicitly named non-first subcommand, so this is a distinct, still-open facet. Note that #576 is already included in the 0.8.1 release, so upgrading does not fix this.
Reproduction
val tool = Command("tool").subcommands(
Command("start", Options.text("port")),
Command("stop")
)
tool.parse(List("tool", "stop", "--help"), CliConfig.default)
- Expected: help for
stop. - Actual: help for
start(e.g.tool start [--port <text>]).
Reproduced on current master, which is identical to the 0.8.1 release in this code path.
Root cause (Command.scala)
-
Command.Single.parse— the final-checkexhaustiveSearchforces this command's own name:if (args.contains("--help") || args.contains("-h")) parseBuiltInArgs(List(name, "--help"))So a
Singlereturns its own help whenever--helpappears anywhere inargs, even whenargsdo not name this command. -
Command.Subcommands.parse— theUserDefinedbranch parses the child with the originalconf(finalCheckBuiltIn = true):child.parse(leftover, conf)instead of the
childConf(finalCheckBuiltIn = false) it already defines and uses in the dedicated help/wizard paths.
Trace for tool stop --help:
- Root
Single("tool")parses, leaving["stop", "--help"]→ theUserDefinedbranch. - The child is a left-leaning
OrElse(start, stop), sostart.parse(["stop", "--help"], conf)runs first. start's name does not matchstop, so normal parsing fails — butfinalCheckBuiltIn == trueandargs.contains("--help"), soexhaustiveSearchfires and returnsstart's help.OrElsesees a success and short-circuits;stopis never tried.
Suggested fix
Reuse childConf in the UserDefined branch (consistent with the help/wizard paths in the same method):
case CommandDirective.UserDefined(leftover, a) if leftover.nonEmpty =>
child.parse(leftover, childConf) // was: conf
With finalCheckBuiltIn = false for the child OrElse, a non-matching alternative fails with CommandMismatch instead of returning its own help, so OrElse advances to the alternative whose name matches. Optionally also guard exhaustiveSearch so it only fires when the leading token in args names this command.
Test gap
The current subcommand-help test only checks git add --help, where add is the first subcommand — which hides this bug. A regression test should name a non-first subcommand, e.g. git clone --help must show clone's help and not the first subcommand's.
Workaround
config = CliConfig.default.copy(finalCheckBuiltIn = false)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in Command.scala, especially Command.Single.parse and Command.Subcommands.parse, then read the existing subcommand-help test. Reproduce the non-first case such as git clone --help, apply the suggested child parsing configuration consistently with the help paths, and add a regression test showing that the named subcommand's help is rendered rather than the first subcommand's.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100