fix(#130e-A): route help/submit/resume --help to help topics before credential check

## What Was Broken (ROADMAP #130e, filed cycle #53)

Three subcommands leaked `missing_credentials` errors when called
with `--help`:

    $ claw help --help
    [error-kind: missing_credentials]
    error: missing Anthropic credentials...

    $ claw submit --help
    [error-kind: missing_credentials]
    error: missing Anthropic credentials...

    $ claw resume --help
    [error-kind: missing_credentials]
    error: missing Anthropic credentials...

This is the same dispatch-order bug class as #251 (session verbs).
The parser fell through to the credential check before help-flag
resolution ran. Critical discoverability gap: users couldn't learn
what these commands do without valid credentials.

## Root Cause (Traced)

`parse_local_help_action()` (main.rs:1260) is called early in
`parse_args()` (main.rs:1002), BEFORE credential check. But the
match statement inside only recognized:
status, sandbox, doctor, acp, init, state, export, version,
system-prompt, dump-manifests, bootstrap-plan, diff, config.

`help`, `submit`, `resume` were NOT in the list, so the function
returned `None`, and parsing continued to credential check which
then failed.

## What This Fix Does

Same pattern as #130c (diff) and #130d (config):

1. **LocalHelpTopic enum extended** with Meta, Submit, Resume variants
2. **parse_local_help_action() extended** to map the three new cases
3. **Help topic renderers added** with accurate usage info

Three-line change to parse_local_help_action:

    "help" => LocalHelpTopic::Meta,
    "submit" => LocalHelpTopic::Submit,
    "resume" => LocalHelpTopic::Resume,

Dispatch order (parse_args):
    1. --resume parsing
    2. parse_local_help_action() ← NOW catches help/submit/resume --help
    3. parse_single_word_command_alias()
    4. parse_subcommand() ← Credential check happens here

## Dogfood Verification

Before fix (all three):
    $ claw help --help
    [error-kind: missing_credentials]
    error: missing Anthropic credentials...

After fix:
    $ claw help --help
    Help
      Usage            claw help [--output-format <format>]
      Purpose          show the full CLI help text (all subcommands, flags, environment)
      ...

    $ claw submit --help
    Submit
      Usage            claw submit [--session <id|latest>] <prompt-text>
      Purpose          send a prompt to an existing managed session
      Requires         valid Anthropic credentials (when actually submitting)
      ...

    $ claw resume --help
    Resume
      Usage            claw resume [<session-id|latest>]
      Purpose          restart an interactive REPL attached to a managed session
      ...

## Non-Regression Verification

- `claw help` (no --help) → still shows full CLI help 
- `claw submit "text"` (with prompt) → still requires credentials 
- `claw resume` (bare) → still emits slash command guidance 
- All 180 binary tests pass 
- All 466 library tests pass 

## Regression Tests Added (6 assertions)

- `help --help` → routes to HelpTopic(Meta)
- `submit --help` → routes to HelpTopic(Submit)
- `resume --help` → routes to HelpTopic(Resume)
- Short forms: `help -h`, `submit -h`, `resume -h` all work

## Pattern Note

This is Category A of #130e (dispatch-order bugs). Same class as #251.
Category B (surface-parity: plugins, prompt) will be handled in a
follow-up commit/branch.

## Help-Parity Sweep Status

After cycle #52 (#130c diff, #130d config), help sweep revealed:

| Command | Before | After This Commit |
|---|---|---|
| help --help | missing_credentials |  Meta help |
| submit --help | missing_credentials |  Submit help |
| resume --help | missing_credentials |  Resume help |
| plugins --help | "Unknown action" |  #130e-B (next) |
| prompt --help | wrong help |  #130e-B (next) |

## Related

- Closes #130e Category A (dispatch-order help fixes)
- Same bug class as #251 (session verbs)
- Stacks on #130d (config help) on same worktree branch
- #130e Category B (plugins, prompt) queued for follow-up
This commit is contained in:
YeonGyu-Kim 2026-04-23 02:03:10 +09:00
parent 19638a015e
commit 0ca034472b

View File

@ -735,6 +735,10 @@ enum LocalHelpTopic {
Diff, Diff,
// #130d: help parity for `claw config --help` // #130d: help parity for `claw config --help`
Config, Config,
// #130e: help parity — dispatch-order bugs (help, submit, resume)
Meta,
Submit,
Resume,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -1278,6 +1282,10 @@ fn parse_local_help_action(rest: &[String]) -> Option<Result<CliAction, String>>
"diff" => LocalHelpTopic::Diff, "diff" => LocalHelpTopic::Diff,
// #130d: help parity for `claw config --help` // #130d: help parity for `claw config --help`
"config" => LocalHelpTopic::Config, "config" => LocalHelpTopic::Config,
// #130e: help parity — dispatch-order fixes
"help" => LocalHelpTopic::Meta,
"submit" => LocalHelpTopic::Submit,
"resume" => LocalHelpTopic::Resume,
_ => return None, _ => return None,
}; };
Some(Ok(CliAction::HelpTopic(topic))) Some(Ok(CliAction::HelpTopic(topic)))
@ -6119,6 +6127,30 @@ fn render_help_topic(topic: LocalHelpTopic) -> String {
Formats text (default), json Formats text (default), json
Related claw status · claw doctor · claw init" Related claw status · claw doctor · claw init"
.to_string(), .to_string(),
// #130e: help topic for `claw help --help` (meta-help).
LocalHelpTopic::Meta => "Help
Usage claw help [--output-format <format>]
Purpose show the full CLI help text (all subcommands, flags, environment)
Aliases claw --help · claw -h
Formats text (default), json
Related claw <subcommand> --help · claw version"
.to_string(),
// #130e: help topic for `claw submit --help`.
LocalHelpTopic::Submit => "Submit
Usage claw submit [--session <id|latest>] <prompt-text>
Purpose send a prompt to an existing managed session without starting a new one
Defaults --session latest (resumes the most recent managed session)
Requires valid Anthropic credentials (ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY)
Related claw prompt · claw --resume · /session list"
.to_string(),
// #130e: help topic for `claw resume --help`.
LocalHelpTopic::Resume => "Resume
Usage claw resume [<session-id|latest>]
Purpose restart an interactive REPL attached to a managed session
Defaults latest session if no argument provided
Requires valid Anthropic credentials (ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY)
Related claw submit · claw --resume · /session list"
.to_string(),
} }
} }
@ -10467,6 +10499,47 @@ mod tests {
matches!(config_section, CliAction::Config { section: Some(ref s), .. } if s == "permissions"), matches!(config_section, CliAction::Config { section: Some(ref s), .. } if s == "permissions"),
"#130d: config with section must still work" "#130d: config with section must still work"
); );
// #130e: dispatch-order help fixes for help, submit, resume
// These previously emitted `missing_credentials` instead of showing help,
// because parse_local_help_action() didn't route them. Now they route
// to dedicated help topics before credential check.
let help_help = parse_args(&[
"help".to_string(),
"--help".to_string(),
])
.expect("help --help must parse as help action");
assert!(
matches!(help_help, CliAction::HelpTopic(LocalHelpTopic::Meta)),
"#130e: help --help must route to LocalHelpTopic::Meta, got: {help_help:?}"
);
let submit_help = parse_args(&[
"submit".to_string(),
"--help".to_string(),
])
.expect("submit --help must parse as help action");
assert!(
matches!(submit_help, CliAction::HelpTopic(LocalHelpTopic::Submit)),
"#130e: submit --help must route to LocalHelpTopic::Submit"
);
let resume_help = parse_args(&[
"resume".to_string(),
"--help".to_string(),
])
.expect("resume --help must parse as help action");
assert!(
matches!(resume_help, CliAction::HelpTopic(LocalHelpTopic::Resume)),
"#130e: resume --help must route to LocalHelpTopic::Resume"
);
// Short form `-h` works for all three
let help_h = parse_args(&["help".to_string(), "-h".to_string()])
.expect("help -h must parse");
assert!(matches!(help_h, CliAction::HelpTopic(LocalHelpTopic::Meta)));
let submit_h = parse_args(&["submit".to_string(), "-h".to_string()])
.expect("submit -h must parse");
assert!(matches!(submit_h, CliAction::HelpTopic(LocalHelpTopic::Submit)));
let resume_h = parse_args(&["resume".to_string(), "-h".to_string()])
.expect("resume -h must parse");
assert!(matches!(resume_h, CliAction::HelpTopic(LocalHelpTopic::Resume)));
// #147: empty / whitespace-only positional args must be rejected // #147: empty / whitespace-only positional args must be rejected
// with a specific error instead of falling through to the prompt // with a specific error instead of falling through to the prompt
// path (where they surface a misleading "missing Anthropic // path (where they surface a misleading "missing Anthropic