Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions rust/crates/rusty-claude-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,9 @@ Run `claw --help` for usage."
/// matching against the error messages produced throughout the CLI surface.
fn classify_error_kind(message: &str) -> &'static str {
// Check specific patterns first (more specific before generic)
if message.starts_with("command_not_found:") {
if message.starts_with("unknown_slash_command:") {
"unknown_slash_command"
} else if message.starts_with("command_not_found:") {
"command_not_found"
} else if message.contains("missing Anthropic credentials") {
"missing_credentials"
Expand Down Expand Up @@ -1764,7 +1766,10 @@ fn format_unknown_option(option: &str) -> String {
}

fn format_unknown_direct_slash_command(name: &str) -> String {
let mut message = format!("unknown slash command outside the REPL: /{name}");
// #827: prefix with classifier-friendly token so classify_error_kind
// returns "unknown_slash_command" instead of the opaque fallback.
let mut message =
format!("unknown_slash_command: unknown slash command outside the REPL: /{name}");
if let Some(suggestions) = render_suggestion_line("Did you mean", &suggest_slash_commands(name))
{
message.push('\n');
Expand All @@ -1779,7 +1784,9 @@ fn format_unknown_direct_slash_command(name: &str) -> String {
}

fn format_unknown_slash_command(name: &str) -> String {
let mut message = format!("Unknown slash command: /{name}");
// #827: prefix with classifier-friendly token so classify_error_kind
// can return "unknown_slash_command" instead of the opaque fallback.
let mut message = format!("unknown_slash_command: Unknown slash command: /{name}");
if let Some(suggestions) = render_suggestion_line("Did you mean", &suggest_slash_commands(name))
{
message.push('\n');
Expand Down
26 changes: 26 additions & 0 deletions rust/crates/rusty-claude-cli/tests/output_format_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3967,3 +3967,29 @@ fn multi_word_unknown_subcommand_falls_through_to_prompt_826() {
"multi-word fallthrough JSON must have empty stderr: {stderr:?}"
);
}

// #827: direct /unknown-slash-command must emit typed error_kind, not "unknown"
// Uses the direct-slash CLI path (no session load needed; reproducible on CI).
#[test]
fn direct_unknown_slash_command_emits_typed_error_kind() {
let root = unique_temp_dir("direct-unknown-slash-827");
std::fs::create_dir_all(&root).expect("create temp dir");
let output = run_claw(&root, &["--output-format", "json", "/boguscommand"], &[]);
assert_eq!(output.status.code(), Some(1), "unknown slash should exit 1");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let j: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("unknown slash must emit JSON (#827)");
assert_ne!(
j["error_kind"], "unknown",
"direct unknown slash must not emit opaque \'unknown\' error_kind (#827): {j}"
);
assert_eq!(
j["error_kind"], "unknown_slash_command",
"direct unknown slash must emit unknown_slash_command (#827): {j}"
);
assert!(
stderr.is_empty(),
"direct unknown slash JSON must have empty stderr (#827)"
);
}
Loading