diff --git a/ROADMAP.md b/ROADMAP.md index be1184b6..f7111c6d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7729,3 +7729,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 780. **`classify_error_kind` arm ordering bug: `"failed to restore session: legacy session is missing workspace binding: ..."` classified as `session_load_failed` instead of `legacy_session_no_workspace_binding`** — dogfooded 2026-05-27 on `364e7909`. The full error message from `resume_session` prepends `"failed to restore session: "` before `"legacy session is missing workspace binding: ..."`. The `contains("failed to restore session")` arm at line 278 matched first, returning `session_load_failed`; the more specific `legacy_session_no_workspace_binding` arm at line 282 was never reached. Same shadowing existed for `no_managed_sessions`. Fix: reordered the three arms — specific cases (`no_managed_sessions`, `legacy_session_no_workspace_binding`) before the generic `session_load_failed` catch-all. Unit test updated to assert corrected discriminants, plus new assertion covering the full prefixed message that exposed the bug. 40 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori classifier-ordering probe on `364e7909`, 2026-05-27. 781. **`api_http_error` was a single bucket for all HTTP errors; 401 auth and 429 rate-limit returned `hint:null` with no distinction** — dogfooded 2026-05-27 on `d9844cfe`. `classify_error_kind` had a single `api_http_error` arm for all API failures. 401 Unauthorized and 429 rate-limit errors emitted `error_kind:"api_http_error"` + `hint:null`, making it impossible for automation to distinguish auth misconfiguration from transient rate-limiting. Fixes: (1) added `api_auth_error` sub-classifier arm for 401/Unauthorized/authentication_error messages; (2) added `api_rate_limit_error` arm for 429/rate_limit messages; (3) added `fallback_hint_for_error_kind()` that derives a stable hint from the error kind when `split_error_hint` returns `None` (API layer never emits `\n`-delimited hints); (4) main JSON error emission path now calls `fallback_hint_for_error_kind` as fallback. Auth errors now return `api_auth_error` + env-var hint; rate-limit returns `api_rate_limit_error` + retry hint. Unit tests updated. 40 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori API error opacity probe on `d9844cfe`, 2026-05-27. + +782. **`claw acp start` returned `error_kind:"unsupported_acp_invocation"` + `hint:null` — remediation text was on same line** — dogfooded 2026-05-27 on `16c1117a` (pinpoint by Gaebal-gajae). The error message `"unsupported ACP invocation. Use `claw acp`, `claw acp serve`, `claw --acp`, or `claw -acp`."` had no `\n` delimiter, so `split_error_hint` returned `hint:null`. Automation could tell ACP was unsupported but could not read the remediation structurally. Fix: inserted a `\n` before the remediation text: `"unsupported ACP invocation. Use ... claw -acp.\nACP/Zed editor integration is currently a discoverability alias only; ..."`. Integration test `acp_unsupported_invocation_has_hint_782` added. 41 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `16c1117a`, 2026-05-27. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 4a144870..268d7f44 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1593,7 +1593,7 @@ fn parse_acp_args(args: &[String], output_format: CliOutputFormat) -> Result Ok(CliAction::Acp { output_format }), [subcommand] if subcommand == "serve" => Ok(CliAction::Acp { output_format }), _ => Err(String::from( - "unsupported ACP invocation. Use `claw acp`, `claw acp serve`, `claw --acp`, or `claw -acp`.", + "unsupported ACP invocation. Use `claw acp`, `claw acp serve`, `claw --acp`, or `claw -acp`.\nACP/Zed editor integration is currently a discoverability alias only; a real daemon and JSON-RPC endpoint are in ROADMAP tracking.", )), } } 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 ea88b2cc..739cb503 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -2329,3 +2329,38 @@ fn resume_skills_invocation_is_typed_interactive_only_779() { "hint must reference live session or CLI, got: {hint:?}" ); } + +#[test] +fn acp_unsupported_invocation_has_hint_782() { + // #782: `claw acp start` returned error_kind:unsupported_acp_invocation but hint:null + // because the remediation text was on the same line as the error message. + // Fix: add \n-delimited hint so split_error_hint extracts it. + let root = unique_temp_dir("acp-unsupported-782"); + 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", "acp", "start"], &[]); + assert!(!output.status.success(), "acp start should fail"); + let stderr = String::from_utf8_lossy(&output.stderr); + let json_line = stderr + .lines() + .find(|l| l.trim_start().starts_with('{')) + .expect("should emit JSON error"); + let parsed: serde_json::Value = serde_json::from_str(json_line).unwrap(); + assert_eq!( + parsed["error_kind"], "unsupported_acp_invocation", + "unsupported ACP invocation should be classified correctly" + ); + let hint = parsed["hint"] + .as_str() + .expect("hint must be non-null (#782)"); + assert!(!hint.is_empty(), "hint must not be empty"); + assert!( + hint.contains("discoverability") || hint.contains("ROADMAP"), + "hint should explain the discoverability-only status, got: {hint:?}" + ); +}