fix: unknown single-word subcommand emits command_not_found (#825/#826)

Single-word all-alpha/dash tokens that don't match any known subcommand
now always emit command_not_found (with or without fuzzy suggestions).

Multi-word cases fall through to CliAction::Prompt (natural language
prompt passthrough like 'claw explain this' must still work). The
multi-word gap is documented as ROADMAP #826 (known limitation).

Tests:
- unknown_subcommand_json_emits_command_not_found (new)
- unknown_subcommand_text_emits_command_not_found_on_stderr (new)
- unknown_subcommand_typo_with_suggestions_json_emits_command_not_found (new)
- multi_word_unknown_subcommand_falls_through_to_prompt_826 (documents gap)

572 tests pass, 1 pre-existing worker_boot failure unrelated.
This commit is contained in:
YeonGyu-Kim
2026-05-29 14:58:07 +09:00
committed by GitHub
parent 5458d3547a
commit d47b015100
2 changed files with 31 additions and 3 deletions

View File

@@ -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:?}"
);
}