diff --git a/ROADMAP.md b/ROADMAP.md index a5eb1800..6a8e9d1b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7753,3 +7753,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 792. **`claw agents list --bogus-flag` and `claw skills list --bogus-flag` silently returned `status:"ok" count:0` instead of an error** — dogfooded 2026-05-27 on `93a159dc`. The `list ` arm in both handlers treated flag-shaped tokens (`--something`) as name substring filters. Since no agents/skills have `--bogus` in their name, result was empty success list — a false positive that masks typos and unknown flags. Fix: added flag-prefix guard at the top of both `list ` arms in `commands/src/lib.rs`; detected filter tokens starting with `-` return `unknown_option` + usage hint. Two new integration tests `agents_list_flag_shaped_filter_returns_unknown_option_792`, `skills_list_flag_shaped_filter_returns_unknown_option_792`. 53 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori agents/skills list probe on `93a159dc`, 2026-05-27. 793. **`claw plugins list --bogus-flag` silent empty success + `plugins uninstall ` had `hint:null`** — dogfooded 2026-05-27 on `abfa2e4c`. Two gaps: (1) `plugins list` filter branch in `print_plugins` treated `--bogus-flag` as an id substring filter, found no matches, returned `status:"ok"` empty list — same false-positive as #792 for agents/skills. (2) `plugins uninstall no-such` propagated `plugin_not_found` error via `?` with no `\n` delimiter; `plugin_not_found` was missing from `fallback_hint_for_error_kind` table. Fix: (1) added flag-prefix guard in `print_plugins` `is_list_action` branch (detects tokens starting with `-`, returns `unknown_option` + usage hint, exits 1); (2) added `"plugin_not_found"` → `"Run 'claw plugins list' to see installed plugins."` to fallback table. Two new tests `plugins_list_flag_shaped_filter_returns_unknown_option_793`, `plugins_uninstall_not_found_has_hint_793`. 55 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori plugins lifecycle probe on `abfa2e4c`, 2026-05-27. + +794. **`claw plugins install /nonexistent/path` returned `error_kind:"unknown"` + `hint:null`** — dogfooded 2026-05-27 on `57a57ef7`. The error message `"plugin source '/path' was not found"` had no classifier arm, falling to `"unknown"`. Fix: added `plugin_source_not_found` classifier arm (`message.contains("plugin source") && message.contains("was not found")`); added `"plugin_source_not_found"` → `"Check that the path or URL is correct..."` to `fallback_hint_for_error_kind`. Unit test assertion added to `test_classify_error_kind`; integration test `plugins_install_not_found_path_returns_typed_kind_794` added. 56 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori plugins install probe on `57a57ef7`, 2026-05-27. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 04773003..91bf0a41 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -337,6 +337,9 @@ fn classify_error_kind(message: &str) -> &'static str { "agent_not_found" } else if message.contains("is not installed") { "plugin_not_found" + } else if message.contains("plugin source") && message.contains("was not found") { + // #794: `plugins install /nonexistent/path` → "plugin source ... was not found" + "plugin_source_not_found" } else if (message.contains("skill source") && message.contains("not found")) || message.starts_with("skill '") { @@ -414,6 +417,10 @@ fn fallback_hint_for_error_kind(kind: &str) -> Option<&'static str> { // #793: plugins uninstall/enable/disable of non-existing plugin propagates through // the ? operator with no \n delimiter, so split_error_hint returns None. "plugin_not_found" => Some("Run `claw plugins list` to see installed plugins."), + // #794: plugins install with a path that doesn't exist + "plugin_source_not_found" => Some( + "Check that the path or URL is correct. Use a local directory or a valid registry id.", + ), _ => None, } } @@ -13178,6 +13185,11 @@ mod tests { classify_error_kind("my-plugin is not installed"), "plugin_not_found" ); + // #794: plugins install with missing source path + assert_eq!( + classify_error_kind("plugin source `/nonexistent/path` was not found"), + "plugin_source_not_found" + ); assert_eq!( classify_error_kind("skill source /path/to/skill not found"), "skill_not_found" 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 f14e8675..f0e79a3c 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -3156,3 +3156,48 @@ fn plugins_uninstall_not_found_has_hint_793() { "hint should reference plugins list, got: {h:?}" ); } + +#[test] +fn plugins_install_not_found_path_returns_typed_kind_794() { + // #794: `claw plugins install /nonexistent/path` returned error_kind:"unknown" + hint:null. + // The message "plugin source ... was not found" had no classifier arm; fell to "unknown". + // Fix: added "plugin_source_not_found" classifier arm + fallback hint table entry. + let root = unique_temp_dir("plugins-install-794"); + fs::create_dir_all(&root).expect("temp dir"); + std::process::Command::new("git") + .args(["init", "-q"]) + .current_dir(&root) + .output() + .ok(); + + let output = run_claw( + &root, + &[ + "--output-format", + "json", + "plugins", + "install", + "/nonexistent-path-xyz-794", + ], + &[], + ); + assert!( + !output.status.success(), + "plugins install not-found-path must exit non-zero (#794)" + ); + let stderr = String::from_utf8_lossy(&output.stderr); + let j: serde_json::Value = stderr + .lines() + .find(|l| l.trim_start().starts_with('{')) + .and_then(|l| serde_json::from_str(l).ok()) + .expect("plugins install not-found should emit JSON error envelope"); + assert_eq!( + j["error_kind"], "plugin_source_not_found", + "plugins install not-found should be plugin_source_not_found, got {:?}", + j["error_kind"] + ); + let h = j["hint"] + .as_str() + .expect("plugin_source_not_found must have non-null hint (#794)"); + assert!(!h.is_empty(), "hint must be non-empty"); +}