From d9844cfe8da02ed830af7ffffb7b4eedb9b2af8d Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 27 May 2026 05:34:49 +0900 Subject: [PATCH] =?UTF-8?q?fix(#780):=20classifier=20arm=20ordering=20bug?= =?UTF-8?q?=20=E2=80=94=20legacy=5Fsession=5Fno=5Fworkspace=5Fbinding=20an?= =?UTF-8?q?d=20no=5Fmanaged=5Fsessions=20shadowed=20by=20generic=20session?= =?UTF-8?q?=5Fload=5Ffailed=20arm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ROADMAP.md | 2 ++ rust/crates/rusty-claude-cli/src/main.rs | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 36331c92..0900c86a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7725,3 +7725,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 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` 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. 779. **Resumed `/skills ` 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. + +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. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index cdd3d944..eb24c042 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -274,12 +274,15 @@ fn classify_error_kind(message: &str) -> &'static str { "missing_worker_state" } else if message.contains("session not found") { "session_not_found" - } else if message.contains("failed to restore session") { - "session_load_failed" } else if message.contains("no managed sessions found") { "no_managed_sessions" } else if message.contains("legacy session is missing workspace binding") { + // #780: must precede the generic "failed to restore session" arm — the full + // error message is "failed to restore session: legacy session is missing workspace + // binding: ...", so the specific arm must be checked first. "legacy_session_no_workspace_binding" + } else if message.contains("failed to restore session") { + "session_load_failed" } else if message.contains("unsupported ACP invocation") { "unsupported_acp_invocation" } else if message.contains("unsupported skills action") { @@ -12942,8 +12945,15 @@ mod tests { classify_error_kind("session not found: abc123"), "session_not_found" ); + // #780: "no managed sessions found" is more specific than generic "failed to restore" + // session_load_failed; the reordered classifier now correctly returns no_managed_sessions. assert_eq!( classify_error_kind("failed to restore session: no managed sessions found"), + "no_managed_sessions" + ); + // Bare session load failures that aren't no_managed_sessions or legacy_binding still map here + assert_eq!( + classify_error_kind("failed to restore session: file not found"), "session_load_failed" ); assert_eq!( @@ -12996,6 +13006,12 @@ mod tests { classify_error_kind("legacy session is missing workspace binding"), "legacy_session_no_workspace_binding" ); + // #780: full error string produced by resume_session includes the + // "failed to restore session: " prefix — the specific arm must win. + assert_eq!( + classify_error_kind("failed to restore session: legacy session is missing workspace binding: /path/to/session.jsonl"), + "legacy_session_no_workspace_binding" + ); assert_eq!( classify_error_kind("unsupported skills action: bogus. Supported actions: list"), "unsupported_skills_action"