From 1a4d0e4676987d08257a0745b067cdfca1fb149a Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 23 Apr 2026 07:32:10 +0900 Subject: [PATCH] fix(#170): classify 4 additional flag-value/slash-command errors as cli_parse / slash_command_requires_repl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ''` → cli_parse (from: `parse_permission_mode_arg`) 2. `invalid value for --reasoning-effort: ''; must be ...` → cli_parse (from: `--reasoning-effort` validator) 3. `model string cannot be empty` → cli_parse (from: empty --model rejection) 4. `slash command / 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. --- rust/crates/rusty-claude-cli/src/main.rs | 77 ++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 071c9d4..060837d 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -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 --: ` - // messages emitted from the parse_args dispatcher. + // #169: Invalid CLI flag values emitted via `unsupported value for + // --: ` 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 + // ''. 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 / 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