fix(#170): classify 4 additional flag-value/slash-command errors as cli_parse / slash_command_requires_repl

Pinpoint #170: Extended typed-error classifier coverage gap discovered during
dogfood probe 2026-04-23 07:30 Seoul (cycle #95).

The #169 comment claimed to cover `--permission-mode bogus` via the
`unsupported value for --` pattern, but the actual `parse_permission_mode_arg`
message format is `unsupported permission mode 'bogus'` (NO `for --` prefix).
Doc-vs-reality lie in the #169 fix itself — fixed here.

Four classifier gaps closed:

1. `unsupported permission mode '<value>'` → cli_parse
   (from: `parse_permission_mode_arg`)
2. `invalid value for --reasoning-effort: '<value>'; must be ...` → cli_parse
   (from: `--reasoning-effort` validator)
3. `model string cannot be empty` → cli_parse
   (from: empty --model rejection)
4. `slash command /<name> is interactive-only. Start \`claw\` ...` →
   slash_command_requires_repl (NEW kind — more specific than cli_parse)

The fourth pattern gets its own kind (`slash_command_requires_repl`) because
it's a command-mode misuse, not a parse error. Downstream consumers can
programmatically offer REPL-launch guidance.

Side benefit: like #169, the correctly classified cli_parse errors now
auto-trigger the #247 hint synthesizer ("Run `claw --help` for usage.").

Test added:
- `classify_error_kind_covers_flag_value_parse_errors_170_extended`
  (4 positive cases + 2 sanity guards)

Tests: 225/225 pass (+1 from #170).

Typed-error family: #121, #127, #129, #130, #164, #169, #247.

Discovered via systematic probe angle: 'error message pattern audit' \u2014
grep each error emission for pattern, confirm classifier matches.
This commit is contained in:
YeonGyu-Kim 2026-04-23 07:32:10 +09:00
parent b8984e515b
commit 1a4d0e4676

View File

@ -293,16 +293,34 @@ fn classify_error_kind(message: &str) -> &'static str {
// #247: `claw ""` or `claw " "` — a parse error, not `unknown`.
"cli_parse"
} else if message.contains("unsupported value for --") {
// #169: Invalid CLI flag values (e.g., `--output-format xml`,
// `--permission-mode bogus`) are parse errors, not `unknown`.
// This covers all `CliOutputFormat::parse` / `parse_permission_mode_arg`
// rejections and any future `unsupported value for --<flag>: <value>`
// messages emitted from the parse_args dispatcher.
// #169: Invalid CLI flag values emitted via `unsupported value for
// --<flag>: <value>` pattern (e.g., from `CliOutputFormat::parse`).
"cli_parse"
} else if message.contains("missing value for --") {
// #169: Missing required flag values (e.g., `--output-format` with no
// trailing argument) are parse errors, same family as above.
// trailing argument).
"cli_parse"
} else if message.contains("unsupported permission mode") {
// #170: `parse_permission_mode_arg` emits `unsupported permission mode
// '<value>'. Use ...` which does NOT match the `for --` pattern covered
// by #169. Classify explicitly as cli_parse.
"cli_parse"
} else if message.contains("invalid value for --") {
// #170: `--reasoning-effort yolo` emits `invalid value for
// --reasoning-effort: 'yolo'; must be low, medium, or high`. Same family
// as #169 but different prefix word.
"cli_parse"
} else if message.contains("model string cannot be empty") {
// #170: `--model ""` or `--model=` emits this exact message.
// Empty-flag-value rejection, cli_parse family.
"cli_parse"
} else if message.contains("slash command") && message.contains("is interactive-only") {
// #170: Bare slash-command invocation outside REPL emits
// `slash command /<name> is interactive-only. Start \`claw\` and run
// it there, or use \`claw --resume ...\``. This is a command-mode
// misuse — more specific than cli_parse, give it its own kind so
// consumers can offer REPL-launch guidance.
"slash_command_requires_repl"
} else if message.contains("invalid model syntax") {
"invalid_model_syntax"
} else if message.contains("is not yet implemented") {
@ -11150,6 +11168,53 @@ mod tests {
);
}
#[test]
fn classify_error_kind_covers_flag_value_parse_errors_170_extended() {
// #170: Extended classifier coverage discovered during dogfood probe
// 2026-04-23 07:30 Seoul. The #169 comment claimed to cover
// `--permission-mode bogus` but the actual message format is
// `unsupported permission mode 'bogus'` (NO `for --` prefix), so it
// still fell through to `unknown`. Four additional patterns found
// in the same probe.
assert_eq!(
classify_error_kind(
"unsupported permission mode 'bogus'. Use read-only, workspace-write, or danger-full-access."
),
"cli_parse",
"invalid --permission-mode value must classify as cli_parse"
);
assert_eq!(
classify_error_kind(
"invalid value for --reasoning-effort: 'yolo'; must be low, medium, or high"
),
"cli_parse",
"invalid --reasoning-effort value must classify as cli_parse"
);
assert_eq!(
classify_error_kind("model string cannot be empty"),
"cli_parse",
"empty --model value must classify as cli_parse"
);
assert_eq!(
classify_error_kind(
"slash command /diff is interactive-only. Start `claw` and run it there, or use `claw --resume SESSION.jsonl /diff` / `claw --resume latest /diff` when the command is marked [resume] in /help."
),
"slash_command_requires_repl",
"interactive-only slash command must classify as slash_command_requires_repl"
);
// Sanity: must not hijack generic prose that mentions these words.
assert_eq!(
classify_error_kind("some invalid value that has nothing to do with flags"),
"unknown",
"generic `invalid value` prose without `for --` should still fall through"
);
assert_eq!(
classify_error_kind("slash command exists and works fine"),
"unknown",
"generic mention of `slash command` without `interactive-only` should fall through"
);
}
#[test]
fn split_error_hint_separates_reason_from_runbook() {
// #77: short reason / hint separation for JSON error payloads