diff --git a/ROADMAP.md b/ROADMAP.md index 0ebfd247..a6af5fad 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7747,3 +7747,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 789. **`claw --output-format json agents show ` and `plugins show ` both returned exit 0 despite `status:"error"` in the JSON** — dogfooded 2026-05-27 on `abdbf61a`. Skills was fixed in #788 (exit 1 via process::exit). Agents and plugins had the identical gap: `print_agents` had no error check at all (just println + Ok(())); `print_plugins`'s not-found branch used `return Ok(())`. MCP was already fixed in an earlier cycle (#68). Fix: added `is_error` check in `print_agents` JSON path (exit 1 when status=="error"); changed plugins not-found branch from `return Ok(())` to `std::process::exit(1)`. Existing `inventory_commands_emit_structured_json_when_requested` test updated to use `run_claw` directly for the not-found case. Two new tests added: `agents_show_not_found_exits_nonzero_789`, `plugins_show_not_found_exits_nonzero_789`. 49 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori exit-code consistency probe on `abdbf61a`, 2026-05-27. 790. **`claw --output-format json system-prompt ` returned `error_kind:"unknown"` + `hint:null`** — dogfooded 2026-05-27 on `e4c3c1aa`. The unknown-option branch in `parse_print_system_prompt_args` emitted plain `"unknown system-prompt option: {other}"` for all unrecognised options except `--json` (which appended a `\n`-delimited suggestion). All non-`--json` cases fell to `unknown+null`. Fix: replaced bare format string with `unknown_option: ... \n` format for all unknown options; `--json` special case preserves its `--output-format json` suggestion in the hint prefix. Integration test `system_prompt_unknown_option_returns_typed_kind_790` covers both paths. 50 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori system-prompt option probe on `e4c3c1aa`, 2026-05-27. + +791. **`claw config show ` and `claw config set ` returned `unexpected_extra_args` + `hint:null`** — dogfooded 2026-05-27 on `9968a27e`. The config arg parser emitted `"unexpected extra arguments after `claw config {}`: {}"` with no `\n` delimiter, so `split_error_hint` returned `None` and `fallback_hint_for_error_kind("unexpected_extra_args")` also returns `None`. Fix: appended `\nUsage: claw config [env|hooks|model|plugins|mcp|settings]` to the error format string. Integration test `config_extra_args_have_non_null_hint_791` covers both paths. 51 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori config-arg probe on `9968a27e`, 2026-05-27. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index dd7c1aab..4e216757 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1190,8 +1190,9 @@ fn parse_args(args: &[String]) -> Result { let tail = &rest[1..]; let section = tail.first().cloned(); if tail.len() > 1 { + // #791: append \n hint so split_error_hint extracts it and hint is non-null return Err(format!( - "unexpected extra arguments after `claw config {}`: {}", + "unexpected extra arguments after `claw config {}`: {}\nUsage: claw config [env|hooks|model|plugins|mcp|settings]", tail[0], tail[1..].join(" ") )); 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 774df458..79d43b3f 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -2904,3 +2904,70 @@ fn system_prompt_unknown_option_returns_typed_kind_790() { "hint for --json should suggest --output-format json, got: {h2:?}" ); } + +#[test] +fn config_extra_args_have_non_null_hint_791() { + // #791: `claw config show bogus-key` and `claw config set a b` returned + // error_kind:"unexpected_extra_args" + hint:null because the error message + // "unexpected extra arguments after `claw config ...`: ..." had no \n delimiter. + // Fix: appended \n + usage hint to the format string. + let root = unique_temp_dir("config-extra-args-791"); + fs::create_dir_all(&root).expect("temp dir"); + std::process::Command::new("git") + .args(["init", "-q"]) + .current_dir(&root) + .output() + .ok(); + + // config show with extra positional arg + let out1 = run_claw( + &root, + &["--output-format", "json", "config", "show", "bogus-key"], + &[], + ); + assert!(!out1.status.success()); + let stderr1 = String::from_utf8_lossy(&out1.stderr); + let j1: serde_json::Value = stderr1 + .lines() + .find(|l| l.trim_start().starts_with('{')) + .and_then(|l| serde_json::from_str(l).ok()) + .expect("config show extra arg should emit JSON error"); + assert_eq!( + j1["error_kind"], "unexpected_extra_args", + "config show extra arg should be unexpected_extra_args, got {:?}", + j1["error_kind"] + ); + let h1 = j1["hint"] + .as_str() + .expect("unexpected_extra_args must have hint (#791)"); + assert!( + h1.contains("config") || h1.contains("claw"), + "hint should reference config usage, got: {h1:?}" + ); + + // config set with extra positionals + let out2 = run_claw( + &root, + &[ + "--output-format", + "json", + "config", + "set", + "bogus-section.key", + "value", + ], + &[], + ); + assert!(!out2.status.success()); + let stderr2 = String::from_utf8_lossy(&out2.stderr); + let j2: serde_json::Value = stderr2 + .lines() + .find(|l| l.trim_start().starts_with('{')) + .and_then(|l| serde_json::from_str(l).ok()) + .expect("config set extra arg should emit JSON error"); + assert_eq!(j2["error_kind"], "unexpected_extra_args"); + assert!( + j2["hint"].as_str().is_some_and(|h| !h.is_empty()), + "config set extra arg must have non-null hint (#791)" + ); +}