From 47c0226a6151197a6d7262cbf14e7a1fa862a797 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 26 May 2026 01:05:07 +0900 Subject: [PATCH] fix(#708): skills show/info/describe responses now emit action:show instead of action:list; remove duplicate status key from render_skills_report_json --- ROADMAP.md | 2 ++ rust/crates/commands/src/lib.rs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 302aa51d..286cb8fa 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7581,3 +7581,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 706. **`claw skills show --output-format json` silently returns `status:"ok"` with empty `skills:[]` when the named skill does not exist — downstream claws cannot distinguish "no skill installed" from "skill name typo"** — dogfooded 2026-05-26 on `f84799c8`. Reproduction: `claw skills show nonexistent --output-format json` → `{kind:"skills", action:"list", status:"ok", skills:[], summary:{total:0,...}}` exit 0. A claw checking whether a skill is available treats empty success as "no skills installed anywhere" rather than "skill not found". **Fix shape:** return `{kind:"skills", action:"show", status:"error", error_kind:"skill_not_found", requested:""}` + exit 1 when `show ` matches nothing; landed at `...`. Source: Jobdori dogfood on `f84799c8`, 2026-05-26. 707. **`init.rs` test `temp_dir()` uses nanoseconds only — two parallel test runs in the same process can land in the same nanosecond window and collide on the same temp path, causing intermittent test failures** — dogfooded 2026-05-26 on `dedad14a`. `artifacts_with_status_partitions_fresh_and_idempotent_runs` was seen flaking in full-suite parallel runs but passing in isolation. Root cause: `temp_dir()` at `init.rs:383` used only `SystemTime::now().as_nanos()` as the uniqueness token. Two concurrent callers within the same nanosecond window produce the same path. Fix: added `AtomicU64` counter combined with nanoseconds → `rusty-claude-init-{nanos}-{id}` eliminates same-process collisions. Source: Jobdori dogfood on `dedad14a`, 2026-05-26. + +708. **`claw skills show --output-format json` returns `action:"list"` even on the show path — `render_skills_report_json` hardcoded `"action":"list"` for all skill responses including show/info/describe** — dogfooded 2026-05-26 on `26a50d91`. `claw skills show korea-weather --output-format json` returned `{action:"list"}` even when the show path was taken. Also had duplicate `"status":"ok"` key in the JSON object. Fix: renamed `render_skills_report_json` to `render_skills_report_json_with_action(skills, action)` and updated all call sites to pass `"list"` or `"show"` appropriately; removed duplicate status key. Source: Jobdori dogfood on `26a50d91`, 2026-05-26. diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index 9b18ea05..9a59636f 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -2451,7 +2451,7 @@ pub fn handle_skills_slash_command_json(args: Option<&str>, cwd: &Path) -> std:: None | Some("list") => { let roots = discover_skill_roots(cwd); let skills = load_skills_from_roots(&roots)?; - Ok(render_skills_report_json(&skills)) + Ok(render_skills_report_json_with_action(&skills, "list")) } Some(args) if args.starts_with("list ") => { let filter = args["list ".len()..].trim().to_lowercase(); @@ -2461,12 +2461,12 @@ pub fn handle_skills_slash_command_json(args: Option<&str>, cwd: &Path) -> std:: .into_iter() .filter(|s| s.name.to_lowercase().contains(&filter)) .collect(); - Ok(render_skills_report_json(&filtered)) + Ok(render_skills_report_json_with_action(&filtered, "list")) } Some("show" | "info" | "describe") => { let roots = discover_skill_roots(cwd); let skills = load_skills_from_roots(&roots)?; - Ok(render_skills_report_json(&skills)) + Ok(render_skills_report_json_with_action(&skills, "show")) } Some(args) if args.starts_with("show ") @@ -2496,7 +2496,7 @@ pub fn handle_skills_slash_command_json(args: Option<&str>, cwd: &Path) -> std:: "requested": name, })); } - Ok(render_skills_report_json(&matched)) + Ok(render_skills_report_json_with_action(&matched, "show")) } Some("install") => Ok(render_skills_usage_json(Some("install"))), Some(args) if args.starts_with("install ") => { @@ -3719,7 +3719,7 @@ fn render_skills_report(skills: &[SkillSummary]) -> String { lines.join("\n").trim_end().to_string() } -fn render_skills_report_json(skills: &[SkillSummary]) -> Value { +fn render_skills_report_json_with_action(skills: &[SkillSummary], action: &str) -> Value { let active = skills .iter() .filter(|skill| skill.shadowed_by.is_none()) @@ -3727,8 +3727,7 @@ fn render_skills_report_json(skills: &[SkillSummary]) -> Value { json!({ "kind": "skills", "status": "ok", - "action": "list", - "status": "ok", + "action": action, "summary": { "total": skills.len(), "active": active, @@ -5470,8 +5469,9 @@ mod tests { origin: SkillOrigin::SkillsDir, }, ]; - let report = super::render_skills_report_json( + let report = super::render_skills_report_json_with_action( &load_skills_from_roots(&roots).expect("skills should load"), + "list", ); assert_eq!(report["kind"], "skills"); assert_eq!(report["action"], "list");