From d5f0d6ed3e67b784f254e0fbf60c01b2d6f45923 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 26 May 2026 16:38:17 +0900 Subject: [PATCH] fix(#739): skills unknown-subcommand JSON path no longer emits double error envelope; help action not propagated as Err --- ROADMAP.md | 2 ++ rust/crates/rusty-claude-cli/src/main.rs | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index ed124f29..9624a70b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7643,3 +7643,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 737. **Test coverage gap: `doctor --output-format json` `boot_preflight` `details[]` had no assertion that entries are `{key,value}` objects with non-null `value` fields — the #736 double-space separator fix had no regression guard, so a revert or accidental prose-format change would silently re-introduce `value:null` entries** — filed 2026-05-26 on `ad982d20`. Added assertions to `doctor_and_resume_status_emit_json_when_requested` in `output_format_contract.rs`: iterate all `boot_preflight.details[]` entries and assert each has a string `key` and a non-null `value`. Source: Jobdori dogfood on `ad982d20`, 2026-05-26. 738. **`claw /commit --output-format json` (and all other interactive-only slash commands invoked outside a session) emitted `hint: null` — the remediation text was in the `error` prose string but no newline separated the short error from the hint, so `split_error_hint` returned the entire message as `error` and `hint: null`** — dogfooded 2026-05-26 on `c592313d`. The format string `"slash command {cmd} is interactive-only. Start `claw`..."` had no newline, so `split_error_hint` (which splits on `\n`) could not extract the hint. Fix: add `\n` between the short error `"slash command X is interactive-only."` and the remediation text, so callers reading `.hint` get the actionable guidance directly. Source: Jobdori dogfood on `c592313d`, 2026-05-26. + +739. **`claw skills --output-format json` emitted two JSON objects on stdout: first the usage envelope (`action:"help", unexpected:"X"`), then a second error abort envelope (`kind:"unknown", error:"skills command failed"`) — the `print_skills` JSON path returned `Err` on `status:"error"` responses even when the response was a normal usage-display (`action:"help"`), causing the generic error serializer to emit the second envelope** — dogfooded 2026-05-26 on `4c3cb0f3`. Fix: skip the `return Err` path when `action == "help"`; usage envelopes are informational, not fatal errors. The root prompt-dispatch gap (`claw skills bogus` → `CliAction::Prompt` → `missing_credentials` in no-creds env) is a pre-existing auth-gate-on-local-surface issue (ROADMAP #431/#449) and not addressed here. Source: Jobdori dogfood on `4c3cb0f3`, 2026-05-26. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 73b0016e..6408bd7e 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -6080,8 +6080,11 @@ impl LiveCli { CliOutputFormat::Json => { let result = handle_skills_slash_command_json(args, &cwd)?; let is_error = result.get("status").and_then(|v| v.as_str()) == Some("error"); + // #739: action:"help" with unexpected set is a usage response, not a fatal error; + // don't return Err which would emit a second error envelope from the generic path. + let is_help_action = result.get("action").and_then(|v| v.as_str()) == Some("help"); println!("{}", serde_json::to_string_pretty(&result)?); - if is_error { + if is_error && !is_help_action { return Err(result .get("message") .and_then(|v| v.as_str())