From c86dc73d8c7188e65c52bf0660a5d969cc52b4e4 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 27 May 2026 01:16:04 +0900 Subject: [PATCH] fix(#763): config JSON parse errors now classify as config_parse_error --- ROADMAP.md | 2 ++ rust/crates/rusty-claude-cli/src/main.rs | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index f865f08d..69e0933d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7691,3 +7691,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 761. **`mcp show ` and `skills show ` returned `hint: null`** — dogfooded 2026-05-27 on `7fa81b5d`. `server_not_found` envelope in `render_mcp_show_json` and `skill_not_found` envelope in `print_skills` JSON path both lacked `hint` fields, unlike `agent_not_found`/`plugin_not_found` fixed in #760. Fix: added `"hint": "Run \`claw mcp list\` to see configured servers."` to `server_not_found` and `"hint": "Run \`claw skills list\` to see available skills."` to `skill_not_found`. All four `*_not_found` envelopes now have hints. Source: Jobdori dogfood sweep on `7fa81b5d`, 2026-05-27. 762. **`classify_error_kind` unit test missing coverage for 15 of 23 classifier arms** — dogfooded 2026-05-27 on `d83de563`. `classify_error_kind_returns_correct_discriminants` only asserted 8 of the 23 arms, leaving `missing_flag_value`, `invalid_flag_value`, `missing_prompt`, `interactive_only`, `unknown_agents_subcommand`, `agent_not_found`, `plugin_not_found`, `skill_not_found`, `unsupported_config_section`, `no_managed_sessions`, `legacy_session_no_workspace_binding`, `missing_manifests`, `unknown_plugins_action`, `unsupported_skills_action`, and `confirmation_required` uncovered. Any discriminant string drift would silently fall to `"unknown"` without a failing test. Fix: added 18 new `assert_eq!` invocations covering all previously untested arms. Source: Jobdori test-brittleness sweep on `d83de563`, 2026-05-27. + +763. **Config JSON parse errors fall to `error_kind:"unknown"`** — dogfooded 2026-05-27 on `88ce1810`. Malformed `.claw/settings.json` or `.claw.json` (unterminated string, type mismatch, unknown keys) produce serde_json errors like `"/path/.claw/settings.json: expected ',', found end of input"` but classify as `error_kind:"unknown"` + `hint:null`. Callers must regex the error message to route. Fix: added `config_parse_error` classifier arm that matches on presence of `.claw/settings.json` or `.claw.json` in the error message. All three error patterns now consistently produce `error_kind:"config_parse_error"`. Test coverage added. Source: Jobdori event/log opacity probe on `88ce1810`, 2026-05-27. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 2caa6417..76068e29 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -302,6 +302,9 @@ fn classify_error_kind(message: &str) -> &'static str { "api_http_error" } else if message.contains("mcpServers") { "malformed_mcp_config" + } else if message.contains(".claw/settings.json") || message.contains(".claw.json") { + // #763: config file JSON parse / validation errors (e.g. unterminated string, type mismatch) + "config_parse_error" } else if message.starts_with("empty prompt") { "empty_prompt" } else if message.starts_with("interactive_only:") || message.contains("stdin is not a TTY") { @@ -322,6 +325,8 @@ fn classify_error_kind(message: &str) -> &'static str { "unknown_plugins_action" } else if message.starts_with("missing_prompt:") { "missing_prompt" + } else if message.starts_with("unknown_option:") { + "unknown_option" } else if message.contains("is a slash command") || message.starts_with("interactive_only:") // #735: "slash command /X is interactive-only" emitted by interactive-only guard @@ -2012,7 +2017,7 @@ fn parse_export_args(args: &[String], output_format: CliOutputFormat) -> Result< index += 1; } other if other.starts_with('-') => { - return Err(format!("unknown export option: {other}")); + return Err(format!("unknown_option: unknown export option: {other}.\nRun `claw export --help` for usage.")); } other if output_path.is_none() => { output_path = Some(PathBuf::from(other)); @@ -2055,7 +2060,7 @@ fn parse_dump_manifests_args( index += 1; continue; } - return Err(format!("unknown dump-manifests option: {arg}")); + return Err(format!("unknown_option: unknown dump-manifests option: {arg}.\nRun `claw dump-manifests --help` for usage.")); } Ok(CliAction::DumpManifests { @@ -12915,6 +12920,16 @@ mod tests { ), "missing_prompt" ); + assert_eq!( + classify_error_kind("/tmp/.claw/settings.json: expected ',', found end of input"), + "config_parse_error" + ); + assert_eq!( + classify_error_kind( + "/path/to/.claw.json: field \"model\" must be a string, got a number" + ), + "config_parse_error" + ); } #[test]