Skip to content

Commit 87f4334

Browse files
committed
fix(#785): add unknown_subcommand classifier arm for unknown subcommand: prose prefix
1 parent e628b4b commit 87f4334

3 files changed

Lines changed: 45 additions & 0 deletions

File tree

ROADMAP.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7735,3 +7735,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
77357735
783. **`claw --output-format json init` success envelope was missing `hint` field; idempotent re-init was not structurally detectable** — dogfooded 2026-05-27 on `32c9276f`. The init JSON envelope had no `hint` field (absent, not null), and no field to distinguish a fresh init from a re-init without checking `created.len() == 0`. Orchestrators had to inspect `created` array length to detect idempotent behavior. Fix: (1) added `hint` field to init JSON envelope — fresh path points at `CLAUDE.md + doctor`; idempotent path says "already initialised, run doctor"; (2) added `already_initialized: bool` field — `true` when `created` and `updated` are both empty (all artifacts skipped). Both test cases (fresh + re-init) covered by `init_json_envelope_has_hint_and_already_initialized_783`. 42 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori init-envelope probe on `32c9276f`, 2026-05-27.
77367736

77377737
784. **`claw export` had two opaque arg-error paths returning `error_kind:"unknown"` + `hint:null`** — dogfooded 2026-05-27 on `81fe0ccb` (pinpoint by Gaebal-gajae). `claw export --output` (missing flag value) emitted plain `"missing value for --output"` with no typed prefix; `claw export a.md b.md` (extra positional) emitted plain `"unexpected export argument: second.md"`. Both classified as `unknown+null`. Fix: (1) `--output` missing-value error now uses `missing_flag_value:` prefix + `\n` usage hint; (2) extra positional now uses `unexpected_extra_args:` prefix + `\n` usage hint; (3) classifier `unexpected_extra_args` arm extended to match both `starts_with("unexpected extra arguments")` (prose form, #766) and `starts_with("unexpected_extra_args:")` (typed prefix form, #784). Integration test `export_arg_errors_have_typed_kind_and_hint_784` covers both paths. 43 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `81fe0ccb`, 2026-05-27.
7738+
7739+
785. **`claw dump` (typo/near-miss for dump-manifests) returned `error_kind:"unknown"` — no classifier arm for `"unknown subcommand:"` prose prefix** — dogfooded 2026-05-27 on `e628b4bb`. Any unknown top-level subcommand that triggers the suggestion path emitted `"unknown subcommand: <x>.\nDid you mean <y>"` but `classify_error_kind` had no arm for that prefix; all fell to the `"unknown"` catch-all. The hint was non-null (the suggestion text was extracted by `split_error_hint`) but `error_kind` was undifferentiated. Fix: added `starts_with("unknown subcommand:")` → `"unknown_subcommand"` arm. Unit test assertion + integration test `unknown_subcommand_returns_typed_kind_785` using `claw dump` as the trigger. 44 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori subcommand-classifier probe on `e628b4bb`, 2026-05-27.

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,9 @@ fn classify_error_kind(message: &str) -> &'static str {
349349
} else if message.contains("has been removed.") {
350350
// #765: removed subcommands (login, logout) — hint contains migration guidance
351351
"removed_subcommand"
352+
} else if message.starts_with("unknown subcommand:") {
353+
// #785: typo/unknown top-level subcommand (e.g. `claw dump` → did you mean dump-manifests?)
354+
"unknown_subcommand"
352355
} else if message.starts_with("unexpected extra arguments")
353356
|| message.starts_with("unexpected_extra_args:")
354357
{
@@ -13009,6 +13012,11 @@ mod tests {
1300913012
classify_error_kind("unrecognized argument `--foo` for subcommand `doctor`"),
1301013013
"cli_parse"
1301113014
);
13015+
// #785: unknown top-level subcommand (typo or unrecognised command)
13016+
assert_eq!(
13017+
classify_error_kind("unknown subcommand: dump.\nDid you mean dump-manifests"),
13018+
"unknown_subcommand"
13019+
);
1301213020
assert_eq!(
1301313021
classify_error_kind("unsupported ACP invocation. Use `claw acp`."),
1301413022
"unsupported_acp_invocation"

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2502,3 +2502,38 @@ fn export_arg_errors_have_typed_kind_and_hint_784() {
25022502
"hint must reference export usage, got: {h2:?}"
25032503
);
25042504
}
2505+
2506+
#[test]
2507+
fn unknown_subcommand_returns_typed_kind_785() {
2508+
// #785: `claw dump` (a near-miss for dump-manifests) returned error_kind:"unknown"
2509+
// because the classifier had no arm for "unknown subcommand:" prose prefix.
2510+
// Fix: added "unknown_subcommand" arm in classify_error_kind.
2511+
let root = unique_temp_dir("unknown-subcommand-785");
2512+
fs::create_dir_all(&root).expect("temp dir");
2513+
std::process::Command::new("git")
2514+
.args(["init", "-q"])
2515+
.current_dir(&root)
2516+
.output()
2517+
.ok();
2518+
2519+
// "dump" is close enough to "dump-manifests" to trigger the typo suggestion path
2520+
let output = run_claw(&root, &["--output-format", "json", "dump"], &[]);
2521+
assert!(!output.status.success(), "unknown subcommand should fail");
2522+
let stderr = String::from_utf8_lossy(&output.stderr);
2523+
let j: serde_json::Value = stderr
2524+
.lines()
2525+
.find(|l| l.trim_start().starts_with('{'))
2526+
.and_then(|l| serde_json::from_str(l).ok())
2527+
.expect("unknown subcommand should emit JSON error");
2528+
assert_eq!(
2529+
j["error_kind"], "unknown_subcommand",
2530+
"unknown subcommand should return unknown_subcommand kind, got {:?}",
2531+
j["error_kind"]
2532+
);
2533+
// hint should point at the suggestion and/or --help
2534+
let hint = j["hint"].as_str().unwrap_or("");
2535+
assert!(
2536+
hint.contains("dump-manifests") || hint.contains("--help") || hint.contains("claw"),
2537+
"hint should reference the suggested subcommand or help, got: {hint:?}"
2538+
);
2539+
}

0 commit comments

Comments
 (0)