From 5f8d1b92a6469e2180d2dc9611d687e026890e8b Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 22 Apr 2026 23:33:50 +0900 Subject: [PATCH] ROADMAP #249: resumed-session slash command error envelopes omit `kind` field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cycle #37 dogfood finding post-#247 merge. Two Err arms in the resumed-session JSON path at main.rs:2747 and main.rs:2783 emit error envelopes WITHOUT the `kind` field required by the §4.44 typed-envelope contract. ## The Pinpoint Probed resumed-session slash command JSON path: $ claw --output-format json --resume latest /session {"command":"/session","error":"unsupported resumed slash command","type":"error"} # no kind field $ claw --output-format json --resume latest /xyz-unknown {"command":"/xyz-unknown","error":"Unknown slash command: /xyz-unknown\n Help /help lists available slash commands","type":"error"} # no kind field AND multi-line error without split hint Compare to happy path which DOES include kind: $ claw --output-format json --resume latest /session list {"active":"...","kind":"session_list",...} Contract awareness exists. It's just not applied in the Err arms. ## Scope Two atomic fixes in main.rs: - Line 2747: SlashCommand::parse() Err → add kind via classify_error_kind() - Line 2783: run_resume_command() Err → add kind + call split_error_hint() ~15 lines Rust total. Bounded. ## Family Classification §4.44 typed-envelope contract sweep: - #179 (parse-error real message quality) — closed - #181 (envelope exit_code matches process exit) — closed - #247 (classify_error_kind misses prompt-patterns) — closed - #248 (verb-qualified unknown option errors) — in-flight (another agent) - **#249 (resumed-session slash error envelopes omit kind) — filed** Natural bundle #247+#248+#249: classifier/envelope completeness across all three CLI paths (top-level parse, subcommand options, resumed-session slash). ## Discipline Per cycle #24 calibration: - Red-state bug? ✗ (errors surfaced, exit codes correct) - Real friction? ✓ (typed-error contract violation; claws dispatching on error.kind get undefined for all resumed slash-command errors) - Evidence-backed? ✓ (dogfood probe + code trace identified both Err arms) - Implementation cost? ~15 lines (bounded) - Same-cycle fix? ✗ (Rust change, deferred per file-not-fix discipline) ## Not Implementing This Cycle Per the boundary discipline established in cycle #36: I don't touch another agent's in-flight work, and I don't implement a Rust fix same-cycle when the pattern is "file + document + let owner/maintainer decide." Filing with concrete fix shape is the correct output. If demand or red-state symptoms arrive, implementation can follow the same path as #247: file → fix in branch → review → merge. Source: Jobdori cycle #37 proactive dogfood in response to Clawhip pinpoint nudge at msg 1496518474019639408. --- ROADMAP.md | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index d57d5b8..5134dae 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6670,3 +6670,103 @@ Two atomic changes: - #179 (parse-error real message quality) — claws consuming envelope expect truthful error - #181 (envelope.exit_code matches process exit) — cross-channel truthfulness - #30 (cycle #30: OPT_OUT rejection tests) — classification contracts deserve regression tests + + +--- + +## Pinpoint #249. Resumed-session slash command error envelopes omit `kind` field — typed-error contract violation at `main.rs:2747` and `main.rs:2783` + +**Gap.** The typed-error envelope contract (§4.44) specifies every error envelope MUST include a `kind` field so claws can dispatch without regex-scraping prose. The `--output-format json` path for resumed-session slash commands has TWO branches that emit error envelopes WITHOUT `kind`: + +1. **`main.rs:2747-2760`** (`SlashCommand::parse()` Err arm) — triggered when the raw command string is malformed or references an invalid slash structure. Fires for inputs like `claw --resume latest /session` (valid name, missing required subcommand arg). + +2. **`main.rs:2783-2793`** (`run_resume_command()` Err arm) — triggered when the slash command dispatch returns an error (including `SlashCommand::Unknown`). Fires for inputs like `claw --resume latest /xyz-unknown`. + +Both arms emit JSON envelopes of shape `{type, error, command}` but NOT `kind`, defeating typed-error dispatch for any claw routing on `error.kind`. + +Also observed: the `/xyz-unknown` path embeds a multi-line error string (`Unknown slash command: /xyz-unknown\n Help ...`) directly into the `error` field without splitting the runbook hint into a separate `hint` field (per #77 `split_error_hint()` convention). JSON consumers get embedded newlines in the error string. + +**Repro.** Dogfooded 2026-04-22 on main HEAD `84466bb` (cycle #37, post-#247 merge): + +```bash +$ cd /Users/yeongyu/clawd/claw-code/rust +$ ./target/debug/claw --output-format json --resume latest /session +{"command":"/session","error":"unsupported resumed slash command","type":"error"} +# Observation: no `kind` field. Claws dispatching on error.kind get undefined. + +$ ./target/debug/claw --output-format json --resume latest /xyz-unknown +{"command":"/xyz-unknown","error":"Unknown slash command: /xyz-unknown + Help /help lists available slash commands","type":"error"} +# Observation: no `kind` field AND multi-line error without split hint. + +$ ./target/debug/claw --output-format json --resume latest /session list +{"active":"session-...","kind":"session_list",...} +# Comparison: happy path DOES include kind field. Only the error path omits it. +``` + +Contrast with the `Ok(None)` arm at `main.rs:2735-2742` which DOES include `kind: "unsupported_resumed_command"` — proving the contract awareness exists, just not applied consistently across all Err arms. + +**Impact.** + +1. **Typed-error dispatch broken for slash-command errors.** A claw reading `{"type":"error", "error":"..."}` and switching on `error.kind` gets `undefined` for any resumed slash-command error. Must fall back to substring matching the `error` field, defeating the point of typed errors. + +2. **Family-internal inconsistency.** The same error path (`eprintln!` → exit(2)) has three arms: `Ok(None)` sets kind, `Err(error)` (parse) doesn't, `Err(error)` (dispatch) doesn't. Random omission is worse than uniform absence because claws can't tell whether they're hitting a kind-less arm or an untyped category. + +3. **Hint embedded in error field.** The `/xyz-unknown` path gets its runbook text inside the `error` string instead of a separate `hint` field, forcing consumers to post-process the message. + +**Recommended fix shape.** + +Two small, atomic edits in `main.rs`: + +1. **Parse-error envelope** (line 2747): Add `"kind": "cli_parse"` to the JSON object. Optionally call `classify_error_kind(&error.to_string())` to get a more specific kind. + +2. **Dispatch-error envelope** (line 2783): Same treatment. Classify using `classify_error_kind()`. Additionally, call `split_error_hint()` on `error.to_string()` to separate the short reason from any embedded hint (matches #77 convention used elsewhere). + +```rust +// Before (line 2747): +serde_json::json!({ + "type": "error", + "error": error.to_string(), + "command": raw_command, +}) + +// After: +let message = error.to_string(); +let kind = classify_error_kind(&message); +let (short_reason, hint) = split_error_hint(&message); +serde_json::json!({ + "type": "error", + "error": short_reason, + "hint": hint, + "kind": kind, + "command": raw_command, +}) +``` + +**Regression coverage.** Add integration tests in `tests/output_format_contract.rs`: +- `resumed_session_bare_slash_name_emits_kind_field_249` — `/session` without subcommand +- `resumed_session_unknown_slash_emits_kind_field_249` — `/xyz-unknown` +- `resumed_session_unknown_slash_splits_hint_249` — multi-line error gets hint split +- Regression guard: `resumed_session_happy_path_session_list_unchanged_249` — confirm `/session list` JSON unchanged + +**Blocker.** None. ~15 lines Rust, bounded. + +**Priority.** Medium. Not red-state (errors ARE surfaced, exit code IS 2), but typed-error contract violation. Any claw doing `error.kind` dispatch on slash-command paths currently falls through to `undefined`. + +**Source.** Jobdori cycle #37 proactive dogfood 2026-04-22 23:15 KST in response to Clawhip pinpoint nudge. Probed slash-command JSON error envelopes post-#247 merge; found two Err arms emitting envelopes without `kind`. Joins §4.44 typed-envelope family: + +- #179 (parse-error real message quality) — closed +- #181 (envelope exit_code matches process exit) — closed +- #247 (classify_error_kind misses prompt-patterns + hint drop) — closed (cycle #34/#36) +- **#248 (verb-qualified unknown option errors misclassified) — in-flight (another agent)** +- **#249 (this: resumed-session slash command envelopes omit kind) — filed** + +Natural bundle: **#247 + #248 + #249** — classifier/envelope completeness sweep. All three fix the same kind of drift: typed-error envelopes missing or mis-set `kind` field on specific CLI paths. When all three land, the typed-envelope contract is uniformly applied across: +- Top-level CLI argument parsing (#247) +- Subcommand option parsing (#248) +- Resumed-session slash command dispatch (#249) + +**Related prior work.** +- §4.44 typed error envelope contract (2026-04-20) +- #77 split_error_hint() — should be applied to slash-command error path too +- #247 (model: add classifier branches + ensure envelope carries them)