From 2c3c0f60e71b0f1f714659c9483380cf3cd6bd14 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 27 May 2026 20:05:39 +0900 Subject: [PATCH] fix(#804): agents/skills show in text mode returned wrong error instead of unexpected_extra_args --- ROADMAP.md | 2 ++ rust/crates/commands/src/lib.rs | 26 +++++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 410cf79b..aac43b7e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7772,3 +7772,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 802. **Four `status:"error"` JSON sites in resume-mode and broad-cwd handlers were missing `hint` field** — found 2026-05-27 on `53953a81` via source audit of all `"status": "error"` sites in main.rs. The resume `unsupported_command` (L3433), `unsupported_resumed_command` (L3455), `cli_parse` (L3474), and `broad_cwd` (L4838) handlers all emitted JSON error envelopes with `error_kind` but no `hint` field. Fix: added contextual `hint` string to all four sites. Source audit now shows 0 `status:"error"` JSON objects missing `hint` across entire main.rs. 62 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori source-level audit of all error JSON sites, 2026-05-27. 803. **`claw agents list --bogus`, `skills list --bogus`, and `plugins list --bogus` in text mode silently returned empty success** — dogfooded 2026-05-27 on `fcebf644`. The JSON-mode flag guards added in #792/#793 only covered the JSON branch; the text-mode path through `handle_agents_slash_command`, `handle_skills_slash_command`, and `print_plugins` still passed flag-shaped tokens as substring filters. Fix: added flag-prefix guards to all three text-mode list handlers (agents and skills in `commands/src/lib.rs`, plugins in `main.rs print_plugins`). Also removed the now-redundant JSON-only guard from print_plugins (the early guard catches both modes). Updated `plugins_list_flag_shaped_filter_returns_unknown_option_793` test to check stderr. 62 CLI contract tests pass. [SCOPE: claw-code] + +804. **`claw agents show ` and `claw skills show ` in text mode returned wrong `agent_not_found`/silent empty instead of catching extra args** — dogfooded 2026-05-27 on `bad1b97f`. Parity gap with JSON-mode fix #796: the text-mode show handlers in `commands/src/lib.rs` still used single-split `split_once(' ')` without checking for spaces in the extracted name. Fix: added `contains(' ')` guard to both text-mode show arms; extra tokens now return `unexpected extra arguments` with usage hint. 62 CLI contract tests pass. [SCOPE: claw-code] diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index cc56c12d..24233580 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -2390,22 +2390,30 @@ pub fn handle_agents_slash_command(args: Option<&str>, cwd: &Path) -> std::io::R || args.starts_with("info ") || args.starts_with("describe ") => { - let name = args + let name_raw = args .split_once(' ') .map(|(_, name)| name) .unwrap_or_default() .trim() .to_lowercase(); + // #804: detect extra positional args (parity with JSON-mode fix #796) + if name_raw.contains(' ') { + let extra = name_raw.split_once(' ').map(|(_, e)| e).unwrap_or(""); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("unexpected extra arguments after agent name\nUsage: claw agents show \nUnexpected extra: '{extra}'"), + )); + } let roots = discover_definition_roots(cwd, "agents"); let agents = load_agents_from_roots(&roots)?; let matched: Vec<_> = agents .into_iter() - .filter(|a| a.name.to_lowercase() == name) + .filter(|a| a.name.to_lowercase() == name_raw) .collect(); if matched.is_empty() { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, - format!("agent not found: {name}"), + format!("agent not found: {name_raw}"), )); } Ok(render_agents_report(&matched)) @@ -2578,17 +2586,25 @@ pub fn handle_skills_slash_command(args: Option<&str>, cwd: &Path) -> std::io::R || args.starts_with("info ") || args.starts_with("describe ") => { - let name = args + let name_raw = args .split_once(' ') .map(|(_, name)| name) .unwrap_or_default() .trim() .to_lowercase(); + // #804: detect extra positional args (parity with JSON-mode fix #796) + if name_raw.contains(' ') { + let extra = name_raw.split_once(' ').map(|(_, e)| e).unwrap_or(""); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("unexpected extra arguments after skill name\nUsage: claw skills show \nUnexpected extra: '{extra}'"), + )); + } let roots = discover_skill_roots(cwd); let skills = load_skills_from_roots(&roots)?; let matched: Vec<_> = skills .into_iter() - .filter(|s| s.name.to_lowercase() == name) + .filter(|s| s.name.to_lowercase() == name_raw) .collect(); Ok(render_skills_report(&matched)) }