diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 531b0b0a..739f06a6 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1381,9 +1381,9 @@ fn parse_args(args: &[String]) -> Result { // #825: always emit a command_not_found error for // single-word all-alpha/dash tokens that don't match any // known subcommand — with or without close suggestions. - // Previously, no-suggestion cases fell through silently to - // CliAction::Prompt and triggered a misleading - // `missing_credentials` error after provider startup. + // Multi-word cases fall through to CliAction::Prompt so + // natural language prompts like `claw explain this` work. + // (#826 documents the multi-word gap as a known limitation.) let mut message = format!("command_not_found: unknown subcommand: {other}."); if let Some(suggestions) = suggest_similar_subcommand(other) { if let Some(line) = render_suggestion_line("Did you mean", &suggestions) { diff --git a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs index 5ca1db35..6dd72909 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -3939,3 +3939,31 @@ fn unknown_subcommand_typo_with_suggestions_json_emits_command_not_found() { ); assert!(stderr.is_empty(), "typo JSON must have empty stderr (#825)"); } + +// #826: multi-word unknown subcommand is a known gap — falls through to +// CliAction::Prompt (natural language prompt passthrough like `claw explain this`). +// Single-word typos (#825) are caught; multi-word is documented as backlog. +// This test documents the current behaviour (not the desired fix). +#[test] +fn multi_word_unknown_subcommand_falls_through_to_prompt_826() { + let root = unique_temp_dir("multi-word-gap-826"); + std::fs::create_dir_all(&root).expect("create temp dir"); + // "foobar baz" has no fuzzy suggestion → falls through to Prompt path + // (hits missing_credentials since no API key is set, rc=1) + let output = run_claw(&root, &["--output-format", "json", "foobar", "baz"], &[]); + assert_eq!(output.status.code(), Some(1)); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + // Currently emits missing_credentials (fallthrough gap documented in #826) + let j: serde_json::Value = + serde_json::from_str(stdout.trim()).expect("multi-word fallthrough must emit JSON"); + assert_eq!( + j["status"], "error", + "multi-word fallthrough must be an error: {j}" + ); + // stderr must be empty regardless (JSON mode) + assert!( + stderr.is_empty(), + "multi-word fallthrough JSON must have empty stderr: {stderr:?}" + ); +}