Skip to content

Commit 364e790

Browse files
committed
fix(#779): resumed /skills invocation returns interactive_only error_kind + non-null hint
1 parent fded4f6 commit 364e790

3 files changed

Lines changed: 62 additions & 3 deletions

File tree

ROADMAP.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7723,3 +7723,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
77237723
777. **Resumed `/plugins install|enable|disable|uninstall|update` returned opaque error_kind instead of interactive_only** — dogfooded 2026-05-27 on `2684737d` (pinpoint by Gaebal-gajae). The mutation arm in `run_resume_command` returned a bare single-line error; after #776 it was classified/split by the caller but fell to `error_kind:"unknown"` + `hint:null` because there was no `interactive_only:` prefix. Orchestrators had no stable signal to distinguish "command rejected — switch to REPL" from a transient error. Fix: each mutation verb now returns `interactive_only: /plugins {action} requires a live session...\n...hint...` so the caller emits `error_kind:"interactive_only"` + non-null hint pointing at REPL or direct CLI. Integration test `resume_plugin_mutations_are_typed_interactive_only_777` covers all 5 mutation verbs. 39 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `2684737d`, 2026-05-27.
77247724

77257725
778. **`claw doctor --output-format json` check objects had no `hint` field — all warn/fail remediation was buried in `details_prose`** — dogfooded 2026-05-27 on `e0203036`. Automation had to parse prose strings to find remediation text instead of reading a stable `hint` field. `DiagnosticCheck.json_value()` never emitted a `hint` field. Fix: added `hint: Option<String>` field to `DiagnosticCheck`, added `with_hint()` builder, populated for all warn/fail cases (auth: set env var; config: fix JSON syntax; workspace: git init; boot_preflight: install missing binaries; sandbox: expected on non-Linux). Empty hint string collapses to `null` (ok checks). 39 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori doctor-envelope probe on `e0203036`, 2026-05-27.
7726+
7727+
779. **Resumed `/skills <skill>` invocation returned bare prose → `error_kind:"unknown"` + `hint:null` after #776** — dogfooded 2026-05-27 on `fded4f6b` (pinpoint by Gaebal-gajae). Sibling of #777: the `/skills` invoke-dispatch guard emitted a single-line prose error identical in structure to the pre-#777 plugins mutation guard. After #776's classify/split it fell to `unknown+null` because no `interactive_only:` prefix was present. Fix: replaced with `interactive_only: /skills {skill_name} invocation requires a live session.\n...hint...` format. Integration test `resume_skills_invocation_is_typed_interactive_only_779` added. 40 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `fded4f6b`, 2026-05-27.

rust/crates/rusty-claude-cli/src/main.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4512,9 +4512,12 @@ fn run_resume_command(
45124512
}
45134513
SlashCommand::Skills { args } => {
45144514
if let SkillSlashDispatch::Invoke(_) = classify_skills_slash_command(args.as_deref()) {
4515-
return Err(
4516-
"resumed /skills invocations are interactive-only; start `claw` and run `/skills <skill>` in the REPL".into(),
4517-
);
4515+
// #779: use interactive_only: prefix + \n hint so #776 classify/split emits
4516+
// error_kind:interactive_only + non-null hint instead of unknown+null.
4517+
let skill_name = args.as_deref().unwrap_or("<skill>");
4518+
return Err(format!(
4519+
"interactive_only: /skills {skill_name} invocation requires a live session.\nStart `claw` and run `/skills {skill_name}` inside the REPL, or use `claw -p <prompt>` with skill context."
4520+
).into());
45184521
}
45194522
let cwd = env::current_dir()?;
45204523
Ok(ResumeCommandOutcome {

rust/crates/rusty-claude-cli/tests/output_format_contract.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,3 +2275,57 @@ fn resume_plugin_mutations_are_typed_interactive_only_777() {
22752275
);
22762276
}
22772277
}
2278+
2279+
#[test]
2280+
fn resume_skills_invocation_is_typed_interactive_only_779() {
2281+
// #779: `/skills <skill>` invocation in resume mode returned bare prose;
2282+
// after #776 classify/split it fell to error_kind:"unknown" + hint:null.
2283+
// Fix: use interactive_only: prefix + \n hint so callers get typed fields.
2284+
let root = unique_temp_dir("resume-skills-invocation-779");
2285+
fs::create_dir_all(&root).expect("temp dir should exist");
2286+
std::process::Command::new("git")
2287+
.args(["init", "-q"])
2288+
.current_dir(&root)
2289+
.output()
2290+
.ok();
2291+
let session_file = write_session_fixture(&root, "resume-skills-779", None);
2292+
2293+
// A non-empty skills arg that would classify as Invoke
2294+
let output = run_claw(
2295+
&root,
2296+
&[
2297+
"--resume",
2298+
session_file.to_str().unwrap(),
2299+
"--output-format",
2300+
"json",
2301+
"/skills my-skill",
2302+
],
2303+
&[],
2304+
);
2305+
assert!(
2306+
!output.status.success(),
2307+
"/skills <skill> in resume mode should exit non-zero"
2308+
);
2309+
let stderr = String::from_utf8_lossy(&output.stderr);
2310+
let json_line = stderr
2311+
.lines()
2312+
.find(|l| l.trim_start().starts_with('{'))
2313+
.unwrap_or_else(|| {
2314+
panic!("/skills invocation should emit JSON error, got stderr: {stderr}")
2315+
});
2316+
let parsed: serde_json::Value = serde_json::from_str(json_line).unwrap();
2317+
assert_eq!(
2318+
parsed["error_kind"], "interactive_only",
2319+
"resumed /skills invocation must return interactive_only, got {:?}",
2320+
parsed["error_kind"]
2321+
);
2322+
let hint = parsed["hint"].as_str().unwrap_or("");
2323+
assert!(
2324+
!hint.is_empty(),
2325+
"resumed /skills invocation must have non-null hint (#779)"
2326+
);
2327+
assert!(
2328+
hint.contains("claw") || hint.contains("REPL") || hint.contains("skills"),
2329+
"hint must reference live session or CLI, got: {hint:?}"
2330+
);
2331+
}

0 commit comments

Comments
 (0)