fix: #247 classify prompt-related parse errors + unify JSON hint plumbing

Cycle #34 dogfood follow-through on Jobdori cycle #33 pinpoint (#247 filed
at fbcbe9d). Closes the two typed-error contract drifts surfaced in that
pinpoint against the Rust `claw` binary.

## What was wrong

1. `classify_error_kind()` (main.rs:~251) used substring matching but did
   NOT match two common prompt-related parse errors:
     - "prompt subcommand requires a prompt string"
     - "empty prompt: provide a subcommand..."
   Both fell through to `"unknown"`. §4.44 typed-error contract specifies
   `parse | usage | unknown` as distinct classes, so claws dispatching on
   `error.kind == "cli_parse"` missed those paths entirely.

2. JSON mode dropped the `Run `claw --help` for usage.` hint. Text mode
   appends it at stderr-print time (main.rs:~234) AFTER split_error_hint()
   has already serialized the envelope, so JSON consumers never saw it.
   Text-mode humans got an actionable pointer; machine consumers did not.

## Fix

Two small, targeted edits:

1. `classify_error_kind()`: add explicit branches for "prompt subcommand
   requires" and "empty prompt:" (the latter anchored with `starts_with`
   so it never hijacks unrelated error messages containing the word).
   Both route to `cli_parse`.

2. JSON error render path in `main()`: after calling split_error_hint(),
   if the message carried no embedded hint AND kind is `cli_parse` AND
   the short-reason does not already embed a `claw --help` pointer,
   synthesize the same `Run `claw --help` for usage.` trailer that
   text-mode stderr appends. The embedded-pointer check prevents
   duplication on the `empty prompt: ... (run `claw --help`)` message
   which already carries inline guidance.

## Verification

Direct repro on the compiled binary:

    $ claw --output-format json prompt
    {"error":"prompt subcommand requires a prompt string",
     "hint":"Run `claw --help` for usage.",
     "kind":"cli_parse","type":"error"}

    $ claw --output-format json ""
    {"error":"empty prompt: provide a subcommand (run `claw --help`) or a non-empty prompt string",
     "hint":null,"kind":"cli_parse","type":"error"}

    $ claw --output-format json doctor --foo   # regression guard
    {"error":"unrecognized argument `--foo` for subcommand `doctor`",
     "hint":"Run `claw --help` for usage.",
     "kind":"cli_parse","type":"error"}

Text mode unchanged in shape; `[error-kind: ...]` prefix now reads
`cli_parse` for the two previously-misclassified paths.

## Regression coverage

- Unit test `classify_error_kind_covers_prompt_parse_errors_247`: locks
  both patterns route to `cli_parse` AND that generic "prompt"-containing
  messages still fall through to `unknown`.
- Integration tests in `tests/output_format_contract.rs`:
  * prompt_subcommand_without_arg_emits_cli_parse_envelope_with_hint_247
  * empty_positional_arg_emits_cli_parse_envelope_247
  * whitespace_only_positional_arg_emits_cli_parse_envelope_247
  * unrecognized_argument_still_classifies_as_cli_parse_247_regression_guard
- Full rusty-claude-cli test suite: 218 tests pass (180 bin unit + 15
  output_format_contract + 12 resume_slash + 7 compact + 3 mock + 1 cli).

## Family / related

Joins §4.44 typed-envelope contract gap family closure: #130, #179, #181,
and now **#247**. All four quartet items now have real fixes landed on
the canonical binary surface rather than only the Python harness.

ROADMAP.md: #247 marked CLOSED with before/after evidence preserved.
This commit is contained in:
YeonGyu-Kim
2026-04-22 22:43:14 +09:00
parent fbcbe9d8d5
commit 84466bbb6c
3 changed files with 174 additions and 2 deletions

View File

@@ -6565,7 +6565,30 @@ main.py: error: the following arguments are required: command
---
## Pinpoint #247. `classify_error_kind()` misses prompt-related parse errors — "empty prompt" and "prompt subcommand requires" classified as `unknown` instead of `cli_parse`; JSON envelope also drops the `Run claw --help for usage` hint
## Pinpoint #247. `classify_error_kind()` misses prompt-related parse errors — "empty prompt" and "prompt subcommand requires" classified as `unknown` instead of `cli_parse`; JSON envelope also drops the `Run claw --help for usage` hint **[CLOSED 2026-04-22 cycle #34]**
**Status.** CLOSED. Fix landed on `feat/jobdori-247-classify-prompt-errors` (cycle #34, Jobdori, 2026-04-22 22:4x KST). Two atomic edits in `rust/crates/rusty-claude-cli/src/main.rs` + one unit test + four integration tests. Verified on the compiled `claw` binary: both prompt-related parse errors now classify as `cli_parse`, and JSON envelopes for the bare-`claw prompt` path now carry the same `Run \`claw --help\` for usage.` hint as text mode. Regression guard locks in that the existing `unrecognized argument` hint/kind path is untouched.
**What landed.**
1. `classify_error_kind()` gained two explicit branches for `prompt subcommand requires` and `empty prompt:`, both routed to `cli_parse`. Patterns are specific enough that generic prompt-adjacent messages still fall through to `unknown` (locked by unit test).
2. JSON error path in `main()` now synthesizes the `Run \`claw --help\` for usage.` hint when `kind == "cli_parse"` AND the message itself did not already embed one (prevents duplication on the `empty prompt: … (run \`claw --help\`)` path which carries guidance inline).
3. Regression tests added: one unit test (`classify_error_kind_covers_prompt_parse_errors_247`) + four integration tests in `tests/output_format_contract.rs` covering bare `claw prompt`, `claw ""`, `claw " "`, and the `doctor --foo` unrecognized-argument regression guard.
**Cross-channel parity after fix.**
```
$ claw --output-format json prompt
{"error":"prompt subcommand requires a prompt string","hint":"Run `claw --help` for usage.","kind":"cli_parse","type":"error"}
$ claw --output-format json ""
{"error":"empty prompt: provide a subcommand (run `claw --help`) or a non-empty prompt string","hint":null,"kind":"cli_parse","type":"error"}
```
Text mode remains unchanged (still prints `[error-kind: cli_parse]` + trailer). Both channels now carry `kind == cli_parse` and the hint content is either explicit (JSON field) or embedded (inline in `error`), closing the typed-envelope asymmetry flagged in the pinpoint.
**Original gap (preserved for history below).**
## Pinpoint #247 (original). `classify_error_kind()` misses prompt-related parse errors — "empty prompt" and "prompt subcommand requires" classified as `unknown` instead of `cli_parse`; JSON envelope also drops the `Run claw --help for usage` hint
**Gap.** Typed-error contract (§4.44) specifies an enumerated error kind set: `filesystem | auth | session | parse | runtime | mcp | delivery | usage | policy | unknown`. The `classify_error_kind()` function at `rust/crates/rusty-claude-cli/src/main.rs:246-280` uses substring matching to map error messages to these kinds. Two common prompt-related parse errors are NOT matched and fall through to `unknown`: