Compare commits

...

44 Commits

Author SHA1 Message Date
Yeachan-Heo 07e62bb4e0 fix(#785): type dump-manifests missing dir errors
Automation needs dump-manifests argument failures to classify without scraping prose, so the --manifests-dir missing-value paths now share the missing_flag_value contract and usage hint.

Constraint: Scope limited to ROADMAP #785 and existing rusty-claude-cli JSON error contracts.
Rejected: Broader dump-manifests parser cleanup | outside the requested ROADMAP #785 packaging scope.
Confidence: high
Scope-risk: narrow
Directive: Keep future dump-manifests argument errors typed and hint-bearing for JSON callers.
Tested: cargo test -p rusty-claude-cli --test output_format_contract -- dump_manifests_missing_dir_has_typed_kind_and_hint_785 --nocapture; cargo test -p rusty-claude-cli classify_error_kind_returns_correct_discriminants -- --nocapture; cargo fmt --all -- --check
Not-tested: full workspace test suite
2026-05-27 00:33:19 +00:00
YeonGyu-Kim e628b4bb68 fix(#784): export --output missing-value and extra-positional errors now return typed error_kind + non-null hint 2026-05-27 08:07:32 +09:00
YeonGyu-Kim 81fe0ccbb7 fix(#783): init JSON envelope now includes hint and already_initialized fields for orchestrator parity 2026-05-27 08:04:15 +09:00
YeonGyu-Kim 32c9276fdb fix(#782): acp unsupported invocation now returns non-null hint with newline-delimited remediation text 2026-05-27 07:37:26 +09:00
YeonGyu-Kim 16c1117af6 fix(#781): sub-classify api_auth_error/api_rate_limit_error from api_http_error; add fallback_hint_for_error_kind for hint-less API errors 2026-05-27 07:34:57 +09:00
YeonGyu-Kim d9844cfe8d fix(#780): classifier arm ordering bug — legacy_session_no_workspace_binding and no_managed_sessions shadowed by generic session_load_failed arm 2026-05-27 05:34:49 +09:00
YeonGyu-Kim 364e7909f4 fix(#779): resumed /skills invocation returns interactive_only error_kind + non-null hint 2026-05-27 05:09:07 +09:00
YeonGyu-Kim fded4f6b11 fix(#778): doctor check JSON objects now include hint field with stable remediation text for warn/fail checks 2026-05-27 05:07:02 +09:00
YeonGyu-Kim e02030364d fix(#777): resumed /plugins mutations return interactive_only error_kind + non-null hint instead of unknown+null 2026-05-27 04:44:06 +09:00
YeonGyu-Kim 2684737d9e fix(#776): resume command errors now return typed error_kind + non-null hint (invalid_history_count, session action errors) 2026-05-27 04:39:43 +09:00
YeonGyu-Kim 028998d040 test(#775): integration tests for #769-#771 interactive-only guards and #774 hint fields; fix stale classifier unit test string 2026-05-27 04:03:52 +09:00
YeonGyu-Kim c760a49c47 fix(#774): agents/plugins/mcp unknown-subcommand errors now include non-null hint 2026-05-27 03:37:00 +09:00
YeonGyu-Kim 727a1ea4a3 fix(#773): config --output-format json now surfaces deprecation warnings in warnings[] array instead of only stderr text 2026-05-27 03:05:14 +09:00
YeonGyu-Kim 212f0b2ad4 fix(#772): slash command aliases now resolve to canonical forms in interactive_only guidance 2026-05-27 02:37:17 +09:00
YeonGyu-Kim bf212b986d fix(#771): init rejects extra args; usage/stats/fork return interactive_only instead of credential check 2026-05-27 02:33:55 +09:00
YeonGyu-Kim 3a1d88386c fix(#770): cost/clear/memory/ultraplan/model with args now return interactive_only instead of falling to credential check 2026-05-27 02:10:41 +09:00
YeonGyu-Kim 9e1be05634 fix(#769): claw session <arg> now returns interactive_only instead of falling to credential check 2026-05-27 02:05:14 +09:00
YeonGyu-Kim b778d4e3d4 fix(#768): --resume non-slash trailing arg now has error_kind:invalid_resume_argument + hint 2026-05-27 01:35:46 +09:00
YeonGyu-Kim 89735dbd33 fix(#766): claw diff extra args now classified as unexpected_extra_args with hint; track #767 session subcommand gap 2026-05-27 01:33:24 +09:00
YeonGyu-Kim d29a8e216b fix(#765): login/logout removed_subcommand now has error_kind + non-null hint 2026-05-27 01:28:35 +09:00
YeonGyu-Kim 4ea255ca6a fix(#764): config_parse_error now populates hint field via Display newline delimiter 2026-05-27 01:23:00 +09:00
YeonGyu-Kim c86dc73d8c fix(#763): config JSON parse errors now classify as config_parse_error 2026-05-27 01:16:04 +09:00
YeonGyu-Kim 88ce181031 test(#762): classify_error_kind now covers all 23 classifier arms (was 8 of 23) 2026-05-27 00:33:11 +09:00
YeonGyu-Kim d83de563c1 fix(#761): mcp server_not_found and skill_not_found envelopes now include hint field 2026-05-27 00:03:53 +09:00
YeonGyu-Kim 7fa81b5dae fix(#760): agent_not_found and plugin_not_found envelopes now include hint field 2026-05-26 23:36:30 +09:00
YeonGyu-Kim ef31328aab fix(#759): validate_model_syntax error strings now use newline separator so hint is non-null 2026-05-26 23:04:04 +09:00
YeonGyu-Kim b8b3af6fc9 fix(#758): --cwd, --date, --session missing-value errors now use missing_flag_value prefix + hint 2026-05-26 22:34:18 +09:00
YeonGyu-Kim 02d77ae1f1 fix(#757): --permission-mode invalid and --allowedTools missing now emit typed error_kind and hint 2026-05-26 22:04:00 +09:00
YeonGyu-Kim 4df146188f fix+test(#756): missing/invalid flag-value errors now emit typed error_kind and non-null hint 2026-05-26 21:37:28 +09:00
YeonGyu-Kim 0e8a449ea9 fix+test(#755): -p consumes exactly one token; flags after prompt text now parse normally 2026-05-26 21:27:39 +09:00
YeonGyu-Kim c70312bd04 fix(#754): missing_credentials hint now newline-delimited so JSON hint field is non-null 2026-05-26 21:23:03 +09:00
YeonGyu-Kim e93271356f fix+test(#753): claw -p (no arg) parity with #750: error_kind:missing_prompt with non-null hint 2026-05-26 20:46:27 +09:00
YeonGyu-Kim cfc26729cf fix(#752): cli_parse unrecognized-arg errors now emit non-null hint for all subcommands 2026-05-26 20:41:12 +09:00
YeonGyu-Kim ddc71b5620 test(#751): regression guard for #750 prompt no-arg error_kind and hint contract 2026-05-26 20:05:34 +09:00
YeonGyu-Kim ac925ed41c fix(#750): claw prompt (no arg) now emits error_kind:missing_prompt with non-null hint 2026-05-26 20:03:14 +09:00
YeonGyu-Kim 2dfb7af66e fix+test(#749): compact interactive-only hint now non-null; extend compact JSON test for hint contract 2026-05-26 19:38:09 +09:00
YeonGyu-Kim 3975f2b3ab fix(#748): mcp unknown subcommand now emits error_kind:unknown_mcp_action matching agents/plugins parity 2026-05-26 19:35:55 +09:00
YeonGyu-Kim 04eb661e57 test(#747): regression guard for #745 bare slash command hint contract (issue/pr/commit) 2026-05-26 19:06:59 +09:00
YeonGyu-Kim 18e7744e42 fix(#746): non-TTY interactive-only error populates hint field via newline split 2026-05-26 19:04:56 +09:00
YeonGyu-Kim 3c5459a33b fix(#745): bare slash command guidance adds newline before hint; claw issue/pr/commit etc now have non-null hint 2026-05-26 18:36:21 +09:00
YeonGyu-Kim 92e053a133 test(#744): regression guard for #741 config unsupported-section hint contract 2026-05-26 18:06:35 +09:00
YeonGyu-Kim 1d5db5f77d fix(#743): plugins help --output-format json now emits usage envelope matching agents/mcp/skills help shape; resolves #420 2026-05-26 18:04:04 +09:00
YeonGyu-Kim 2036f0bd4c test(#742): add git-fixture test for diff changed_file_count dedup; fixes unreachable branch in #740 coverage 2026-05-26 17:41:02 +09:00
YeonGyu-Kim 6e78c1fc8b fix(#741): config unsupported_config_section error now populates hint field; list/show/help verbs get usage hint 2026-05-26 17:38:02 +09:00
8 changed files with 1843 additions and 85 deletions
+91
View File
@@ -7647,3 +7647,94 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
739. **`claw skills <unknown-subcommand> --output-format json` emitted two JSON objects on stdout: first the usage envelope (`action:"help", unexpected:"X"`), then a second error abort envelope (`kind:"unknown", error:"skills command failed"`) — the `print_skills` JSON path returned `Err` on `status:"error"` responses even when the response was a normal usage-display (`action:"help"`), causing the generic error serializer to emit the second envelope** — dogfooded 2026-05-26 on `4c3cb0f3`. Fix: skip the `return Err` path when `action == "help"`; usage envelopes are informational, not fatal errors. The root prompt-dispatch gap (`claw skills bogus``CliAction::Prompt``missing_credentials` in no-creds env) is a pre-existing auth-gate-on-local-surface issue (ROADMAP #431/#449) and not addressed here. Source: Jobdori dogfood on `4c3cb0f3`, 2026-05-26.
740. **Test coverage gap for ROADMAP #733: `diff_json_has_status_and_result_field_702` did not assert `changed_file_count` contract** — dogfooded 2026-05-26 on `d5f0d6ed`. The test asserts `kind`, `status`, `result`, `action`, `working_directory` but not the new `changed_file_count` field added by #733. Coverage gap: (a) no assertion that the field exists, (b) no assertion of numeric type in git repos, (c) no regression guard for dedupe behavior (staged+unstaged to the same file = 1 changed file). Fix: extend the test to assert `changed_file_count: null` in non-git repos and `changed_file_count: u64` in git repos. Source: gaebal-gajae dogfood on `d5f0d6ed`, 2026-05-26.
741. **`claw config list`, `claw config show`, `claw config bogus` --output-format json returned `hint: null` — the unsupported_config_section error envelope had no `hint` field populated, so callers reading `.hint` get null with no actionable guidance** — dogfooded 2026-05-26 on `5d072d21`. The `render_config_json` unsupported-section branch returned a JSON object with `error` (contains the section list) but no `hint` field. Notably `config list` and `config show` are natural verb patterns that users type expecting a list/show subcommand, but claw config uses `claw config` (no args) for list and `claw config <section>` for show — the error gave no indication of this. Fix: add `hint` field to unsupported_config_section error; verbs (`list`, `show`, `help`, `info`) get a hint explaining the correct idiom (`claw config` / `claw config <section>`); other unknown sections get a "not a config section" hint listing valid values. Source: Jobdori dogfood on `5d072d21`, 2026-05-26.
742. **ROADMAP #740 test coverage gap: the new `changed_file_count` branch for git repos was unreachable — the fixture is a plain `unique_temp_dir` (no `git init`), so the test always exercises the `no_git_repo` path and never proves the numeric contract or deduplication behavior** — confirmed by gaebal-gajae on `5d072d21`, fixed on `6e78c1fc`. Fix: add `diff_json_changed_file_count_deduplication_733` test that (a) `git init`s a temp repo, (b) commits a file, (c) asserts `result:"clean"` + `changed_file_count:0`, (d) stages an edit + makes an unstaged edit to the same file, (e) asserts `result:"changes"` + `changed_file_count:1` — proving the BTreeSet deduplication actually works. Source: gaebal-gajae dogfood on `5d072d21`, 2026-05-26.
743. **`claw plugins help --output-format json` returned `error_kind:"unknown_plugins_action"` with `hint:null` instead of the usage envelope (`action:"help", status:"ok", unexpected:null, usage:{...}`) that `agents help`, `mcp help`, and `skills help` all emit — schema drift within the same command family (ROADMAP #420)** — dogfooded 2026-05-26 on `2036f0bd`. Fix: (a) added `Some("help" | "-h" | "--help")` arm to `handle_plugins_slash_command` returning a text usage message (text path parity); (b) added early-return JSON help envelope in `print_plugins` JSON path matching shape of agents/mcp help: `{action:"help", kind:"plugin", status:"ok", unexpected:null, usage:{direct_cli, slash_command}}`. Source: Jobdori dogfood on `2036f0bd`, 2026-05-26.
744. **ROADMAP #741 has no regression test: `claw config list/show/bogus --output-format json hint` field could silently regress to null** — confirmed by gaebal-gajae on `2036f0bd`. Pattern same as #736#737 and #740#742: implementation fix without a pinning test. Fix: add `config_unsupported_section_json_hint_741` test iterating `[list, show, bogus, help]` and asserting `kind:config`, `status:error`, `error_kind:unsupported_config_section`, `hint` is non-empty string, `supported_sections[]` is non-empty. Source: gaebal-gajae dogfood on `2036f0bd`, 2026-05-26.
745. **`claw issue --output-format json` and all other direct-CLI slash commands (pr, commit, etc.) returned `hint: null` — the `bare_slash_command_guidance` message strings had no `\n` separator between short error and remediation text, so `split_error_hint` couldn't populate the hint field** — dogfooded 2026-05-26 on `92e053a1`. The #738 fix added `\n` to the `--resume SESSION /cmd` path but missed the direct-CLI path (e.g. `claw issue`, `claw pr`). The `bare_slash_command_guidance` function formats two message variants: resume-supported and non-resume; both lacked `\n`. Fix: add `\n` before the remediation text in both format strings. Source: Jobdori dogfood on `92e053a1`, 2026-05-26.
746. **`claw --output-format json` (bare, no TTY, no prompt) returned `hint: null` — the non-TTY interactive-only guard error string had no `\n` separator, so `split_error_hint` couldn't extract the remediation text into `.hint`** — dogfooded 2026-05-26 on `3c5459a3`. The single-string message `"interactive_only: claw requires an interactive terminal (stdin is not a TTY and no prompt was provided \u2014 pipe a prompt or run in a TTY)"` contained the hint inline but no newline, so callers reading `.hint` got null and had to parse the prose `error` string. Fix: split at `\n` — short error `"interactive_only: claw requires an interactive terminal."` + hint `"Stdin is not a TTY…pipe a prompt with \`echo 'task' | claw\` or run \`claw\` in an interactive terminal."`. Source: Jobdori dogfood on `3c5459a3`, 2026-05-26.
747. **ROADMAP #745 has no regression test: `claw issue/pr/commit --output-format json hint` could silently regress to null** — confirmed by gaebal-gajae on `3c5459a33`. Same pattern as #737, #742, #744. Fix: add `bare_slash_command_hint_745` test iterating `issue`, `pr`, `commit` and asserting `error_kind:"interactive_only"` + non-empty `hint` field. Source: gaebal-gajae dogfood on `3c5459a33`, fixed on `18e7744e`, 2026-05-26.
748. **`claw mcp bogussubcmd --output-format json` returned `error_kind: null` when an unknown subcommand was passed — `render_mcp_usage_json(Some("bogus"))` set `status:"error"` but left `error_kind` absent — while `agents bogussubcmd` emits `error_kind:"unknown_agents_subcommand"`** — dogfooded 2026-05-26 on `04eb661e`. Fix: add `error_kind: "unknown_mcp_action"` to `render_mcp_usage_json` when `unexpected.is_some()`; remains `null` for the `help` path (`unexpected: null`). Source: Jobdori dogfood on `04eb661e`, 2026-05-26.
749. **`claw compact --output-format json` returned `hint: null``compact_interactive_only_error()` returned a single-line string with no `\n` between short error and remediation text, so `split_error_hint` couldn't populate the hint field** — identified by gaebal-gajae on `04eb661e`. Same class as #738 / #745 / #746. Fix: add `\n` before the remediation text in `compact_interactive_only_error`. Regression guard: extended `compact_subcommand_json_help_fails_fast_when_stdin_closed` to also assert `hint` is non-empty and mentions `/compact` or `--resume`. Source: gaebal-gajae dogfood on `04eb661e`, 2026-05-26.
750. **`claw prompt --output-format json` (no text argument) returned `error_kind:"unknown"` and `hint: null`** — dogfooded 2026-05-26 on `2dfb7af6`. The error string `"prompt subcommand requires a prompt string"` had no prefix prefix for classifier and no `\n` for hint extraction. Fix: (a) prefix with `"missing_prompt: "` + newline before usage hint; (b) add `message.starts_with("missing_prompt:")``"missing_prompt"` classifier arm. Result: `error_kind:"missing_prompt"`, `hint:"Usage: claw prompt <text> or echo '<text>' | claw"`. Source: Jobdori dogfood on `2dfb7af6`, 2026-05-26.
751. **ROADMAP #750 has no regression test: `claw prompt --output-format json` no-arg `error_kind` and `hint` could silently regress** — confirmed by gaebal-gajae on `ac925ed4`. Fix: add `prompt_no_arg_json_error_kind_750` test asserting nonzero exit, `error_kind:"missing_prompt"`, non-empty `hint` mentioning `claw prompt` or `echo`. Source: gaebal-gajae dogfood on `ac925ed4`, 2026-05-26.
752. **`claw <subcommand> --output-format json <bogus-arg>` returned `hint: null` for all `cli_parse` errors when an unrecognized positional arg was supplied** — dogfooded 2026-05-26 on `ddc71b56`. Generic `unrecognized argument` format string had no `\n` so `split_error_hint` emitted null hint (only the `--json` special-case added a hint). Fix: add else-branch appending `\nRun `claw <verb> --help` for usage.` to the generic arm. Affected surfaces: `sandbox`, `doctor`, `version`, and any other subcommand routing through the same unrecognized-arg path. Source: Jobdori dogfood on `ddc71b56`, 2026-05-26.
753. **`claw --output-format json -p` (no prompt arg) returned `error_kind:"unknown"` and `hint: null`** — parity gap with #750/#751 which fixed the explicit `prompt` verb. Identified by gaebal-gajae on `ddc71b56`. Fix: same `missing_prompt:` prefix + newline usage hint as #750. Regression guard: `short_p_flag_no_arg_json_error_kind_753` asserting nonzero exit, `error_kind:"missing_prompt"`, non-empty hint mentioning `claw -p` or `claw prompt`. Source: gaebal-gajae dogfood on `ddc71b56`, 2026-05-26.
754. **`missing_credentials` JSON envelope always had `hint: null` even when a contextual hint was available** — dogfooded 2026-05-26 on `e9327135`. `ApiError::Display` for `MissingCredentials` appended the hint via ` — hint: {hint}` (inline, no `\n`), so `split_error_hint()` could not extract it and left the JSON `hint` field null. Fix: change delimiter from ` — hint: ` to `\n` in `api/src/error.rs` Display impl; update two tests in `api/src/error.rs` and `api/src/providers/mod.rs` to assert newline separator. Source: Jobdori dogfood on `e9327135`, 2026-05-26.
755. **`claw -p hello --model sonnet` swallowed `--model sonnet` into the prompt string** — gaebal-gajae pinpoint on `e9327135` (#117 revival). `-p` used `args[index+1..].join(" ")`, consuming all remaining tokens as prompt. Fix: capture exactly one token via `args.get(index+1)`, reject flag-like tokens (`starts_with('-')`) as `missing_prompt`, support `--` sentinel for literal flag-text, then `continue` the flag loop so `--model`/`--output-format`/etc. parse normally. Dispatch via `short_p_prompt` after full flag scan. Regression guard: `short_p_flag_swallows_no_flags_755` asserts `--output-format json` is parsed (not swallowed) and `--model` as prompt-arg is rejected. Source: gaebal-gajae dogfood on `e9327135`, 2026-05-26.
756. **`--reasoning-effort bogus`, `--model` (no value), and sibling missing/invalid flag-value errors all returned `error_kind:"unknown"` + `hint:null`** — gaebal-gajae pinpoint on `0e8a449e`. All `missing value for --X` and `invalid value for --reasoning-effort` error strings were single-line with no classifier arm. Fix: (a) prefix all with `missing_flag_value:` / `invalid_flag_value:` + `\n` usage hint; (b) add `message.starts_with("missing_flag_value:")``"missing_flag_value"` and `message.starts_with("invalid_flag_value:")``"invalid_flag_value"` classifier arms. Covers `--model`, `--output-format`, `--permission-mode`, `--base-commit`, `--reasoning-effort`. Regression guard: `flag_value_errors_have_error_kind_and_hint_756` — invalid `--reasoning-effort HIGH``invalid_flag_value` + hint with valid values; missing `--model``missing_flag_value` + non-null hint. Source: gaebal-gajae dogfood on `0e8a449e`, 2026-05-26.
757. **`--permission-mode bogus` and `--allowedTools` (no value) returned `error_kind:"unknown"` + `hint:null`** — dogfooded 2026-05-26 on `4df14618`. `parse_permission_mode_arg()` error format had no prefix and no `\n`; `--allowedTools` missing-value string was plain. Fix: prefix `parse_permission_mode_arg` error with `invalid_flag_value:` + `\n` valid-values hint (both call sites); prefix `--allowedTools` missing-value with `missing_flag_value:` + `\n` usage hint. Both now classified by existing `missing_flag_value`/`invalid_flag_value` arms added in #756. Source: Jobdori dogfood on `4df14618`, 2026-05-26.
758. **Three remaining `missing value for --X` strings in `parse_init_args` were still untyped** — dogfooded 2026-05-26 on `02d77ae1`. `--cwd`, `--date`, `--session` missing-value errors in the init-args parser used the old plain-string form with no `missing_flag_value:` prefix and no `\n` hint, unlike the main `parse_args` flags fixed in #756/#757. Fix: applied `missing_flag_value:` prefix + `\n` usage hint to all three. `grep '"missing value for --'` now returns zero results outside of test assertions. Source: Jobdori dogfood sweep on `02d77ae1`, 2026-05-26.
759. **`--model badmodel --output-format json` returned `error_kind:"invalid_model_syntax"` but `hint: null`** — dogfooded 2026-05-26 on `b8b3af6f`. `validate_model_syntax()` had hint text embedded after a period in the error string (no `\n`), so `split_error_hint()` could not extract it. Affected paths: (a) generic invalid format `"invalid model syntax: '{}'. Expected ..."` — joined with `.` not `\n`; (b) spaces-in-model `"contains spaces. Use ..."` — same issue; (c) empty model string — no hint at all. Fix: added `\n` before hint text in all three format strings in `validate_model_syntax`. Source: Jobdori dogfood sweep on `b8b3af6f`, 2026-05-26.
760. **`agent_not_found` and `plugin_not_found` error envelopes lacked `hint` field** — dogfooded 2026-05-26 on `ef31328a`. `claw agents show nonexistent-agent --output-format json` returned `error_kind:"agent_not_found"` with `hint: null`; same for `claw plugins show`. Both structured JSON envelopes in `commands/src/lib.rs` and `main.rs` omitted `hint`. Fix: added `"hint": "Run \`claw agents list\` to see available agents."` to the `agent_not_found` envelope; `"hint": "Run \`claw plugins list\` to see available plugins."` to the `plugin_not_found` envelope. Source: Jobdori dogfood sweep on `ef31328a`, 2026-05-26.
761. **`mcp show <nonexistent>` and `skills show <nonexistent>` returned `hint: null`** — dogfooded 2026-05-27 on `7fa81b5d`. `server_not_found` envelope in `render_mcp_show_json` and `skill_not_found` envelope in `print_skills` JSON path both lacked `hint` fields, unlike `agent_not_found`/`plugin_not_found` fixed in #760. Fix: added `"hint": "Run \`claw mcp list\` to see configured servers."` to `server_not_found` and `"hint": "Run \`claw skills list\` to see available skills."` to `skill_not_found`. All four `*_not_found` envelopes now have hints. Source: Jobdori dogfood sweep on `7fa81b5d`, 2026-05-27.
762. **`classify_error_kind` unit test missing coverage for 15 of 23 classifier arms** — dogfooded 2026-05-27 on `d83de563`. `classify_error_kind_returns_correct_discriminants` only asserted 8 of the 23 arms, leaving `missing_flag_value`, `invalid_flag_value`, `missing_prompt`, `interactive_only`, `unknown_agents_subcommand`, `agent_not_found`, `plugin_not_found`, `skill_not_found`, `unsupported_config_section`, `no_managed_sessions`, `legacy_session_no_workspace_binding`, `missing_manifests`, `unknown_plugins_action`, `unsupported_skills_action`, and `confirmation_required` uncovered. Any discriminant string drift would silently fall to `"unknown"` without a failing test. Fix: added 18 new `assert_eq!` invocations covering all previously untested arms. Source: Jobdori test-brittleness sweep on `d83de563`, 2026-05-27.
763. **Config JSON parse errors fall to `error_kind:"unknown"`** — dogfooded 2026-05-27 on `88ce1810`. Malformed `.claw/settings.json` or `.claw.json` (unterminated string, type mismatch, unknown keys) produce serde_json errors like `"/path/.claw/settings.json: expected ',', found end of input"` but classify as `error_kind:"unknown"` + `hint:null`. Callers must regex the error message to route. Fix: added `config_parse_error` classifier arm that matches on presence of `.claw/settings.json` or `.claw.json` in the error message. All three error patterns now consistently produce `error_kind:"config_parse_error"`. Test coverage added. Source: Jobdori event/log opacity probe on `88ce1810`, 2026-05-27.
764. **`config_parse_error` returned `hint: null` despite #763 adding the classifier** — dogfooded 2026-05-27 on `c86dc73d`. #763 fixed `error_kind` classification but `hint` remained `null` because `ConfigError::Parse` Display impl emitted only the bare serde_json error string (no `\n` delimiter). `split_error_hint()` found nothing to split. Fix: updated `Display for ConfigError::Parse` in `runtime/src/config.rs` to append `\nFix: open the file shown above and correct the JSON syntax, then retry.`. Integration test `config_parse_error_has_typed_error_kind_and_hint_764` added to `output_format_contract.rs` asserting non-zero exit + `error_kind:config_parse_error` + non-empty hint. 31 contract tests pass. Source: Jobdori follow-up probe on `c86dc73d`, 2026-05-27.
765. **`claw login`/`claw logout` returned `error_kind:"unknown"` + `hint:null`** — dogfooded 2026-05-27 on `4ea255ca` (gaebal-gajae pinpoint against `88ce1810`, revised ID after #763/#764 landed). `removed_auth_surface_error()` emitted single-line string with no `\n` delimiter; `split_error_hint()` couldn't extract hint, and no `removed_subcommand` classifier arm existed. Fix: (1) `removed_auth_surface_error()` now emits two-line format (`has been removed.\nSet ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN instead.`); (2) `classify_error_kind()` arm added matching `has been removed.`; (3) unit test assertions and integration test `login_logout_removed_subcommands_have_error_kind_and_hint_765` added verifying both `error_kind:removed_subcommand` and non-null hint mentioning the env var migration. 32 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae + Jobdori probe on `4ea255ca`, 2026-05-27.
766. **`claw diff <extra>` returned `error_kind:"unknown"` + `hint:null`** — dogfooded 2026-05-27 on `d29a8e21`. `claw diff --bogus --output-format json` emitted bare error string `"unexpected extra arguments after \`claw diff\`: --bogus"` with no `\n` delimiter and no classifier arm. Fix: (1) added `\nUsage: claw diff` to the error format string; (2) added `unexpected_extra_args` classifier arm matching `starts_with("unexpected extra arguments")`; (3) unit test assertion + integration test `diff_extra_args_have_typed_error_kind_and_hint_766` added. 33 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori probe on `d29a8e21`, 2026-05-27.
767. **`claw session bogus --output-format json` ignores JSON flag and falls through to credential check** — dogfooded 2026-05-27 on `d29a8e21`. `claw --output-format json session bogus` dispatches to the full interactive REPL runtime instead of rejecting `bogus` as an unknown session subcommand. Output is `error_kind:"missing_credentials"` rather than `error_kind:"unknown_session_subcommand"`. Root cause: `session` arg parser has no unknown-subcommand guard before dispatch; `bogus` is silently accepted as a session ID / switch target and reaches the credential-check gate. Fix needed: validate known session subcommands (`list`, `exists`, `switch`, `fork`, `delete`) before dispatch, return structured `unknown_session_subcommand` error for unrecognized tokens. [SCOPE: claw-code] Source: Jobdori probe on `d29a8e21`, 2026-05-27.
768. **`claw --resume latest compact` returned `error_kind:"unknown"` + `hint:null`** — dogfooded 2026-05-27 on `89735dbd` (gaebal-gajae pinpoint against `d29a8e21`, revised ID after #766/#767 landed). Resume trailing-arg validator emitted single-line `"--resume trailing arguments must be slash commands"` with no typed prefix and no `\n` hint. Fix: (1) changed error to `"invalid_resume_argument: \`{token}\` is not a slash command.\nUsage: claw --resume <session-id|latest> /<slash-command>"` so `split_error_hint()` extracts the hint; (2) added `invalid_resume_argument` classifier arm; (3) unit test assertion + integration test `resume_non_slash_trailing_arg_has_typed_error_kind_and_hint_768` added. 34 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae + Jobdori probe on `89735dbd`, 2026-05-27.
769. **`claw session bogus` fell through to credential check instead of interactive-only guidance** — dogfooded 2026-05-27 on `b778d4e3` (tracked as #767). `claw session <anything>` with more than one token bypassed `parse_single_word_command_alias` (which only fires for `rest.len()==1`) and had no match arm in `parse_args`, so `rest.join(" ")` became a prompt literal dispatched to `CliAction::Prompt`, hitting `missing_credentials` at the gate. Fix: added `"session"` match arm that emits `interactive_only:` error with `\n`-delimited hint referencing `--resume SESSION.jsonl /session` and REPL usage. Integration test `session_with_unknown_subcommand_returns_interactive_only_not_credentials_767` asserts `error_kind:interactive_only` + non-null hint for `bogus`, `nuke`, `delete-all`. 35 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori probe on `b778d4e3`, 2026-05-27.
770. **`claw cost/clear/memory/ultraplan/model` with trailing args fell to credential check** — dogfooded 2026-05-27 on `9e1be056`. Same fallthrough gap as #767/#769: these slash-only verbs had no multi-arg match arms, so `claw cost breakdown`, `claw clear --force`, `claw memory reset`, `claw ultraplan bogus`, `claw model opus extra` all became `CliAction::Prompt` literals, hitting `missing_credentials` at the gate. Fix: added `"cost"`, `"clear"`, `"memory"`, `"ultraplan"`, `"model" if rest.len() > 1` match arms, each returning `interactive_only:` + `\n`-delimited hint. Integration test `slash_only_verbs_with_args_return_interactive_only_not_credentials_770` asserts all five cases. 36 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori sweep on `9e1be056`, 2026-05-27.
771. **`init extraarg` silently succeeded; `usage`/`stats`/`fork` with args fell to credential check** — dogfooded 2026-05-27 on `3a1d8838`. Two distinct gaps: (1) `claw init extraarg` returned `status:ok` with trailing positional ignored — `"init"` arm always returned `Ok(CliAction::Init)` regardless of `rest[1..]`; (2) `claw usage extra`, `claw stats extra`, `claw fork newbranch` had no match arms and fell to `CliAction::Prompt` + credential gate. Fixes: (1) added extra-arg check in `"init"` arm — rejects with `unexpected_extra_args:` prefix + `\n` usage hint; (2) added `"usage"`, `"stats"`, `"fork"` interactive-only arms. All four now return correct `error_kind` + non-null hint. 36 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori sweep on `3a1d8838`, 2026-05-27.
772. **Slash command aliases bypassed `bare_slash_command_guidance` lookup** — dogfooded 2026-05-27 on `bf212b98`. `bare_slash_command_guidance()` only checked `spec.name == command_name`, not `spec.aliases`, so `claw yes`, `claw no`, `claw y`, `claw n`, `claw skill`, `claw cwd` all fell through (either to typo suggestions or `missing_credentials`). Should have returned `interactive_only:` guidance referencing the canonical form. Fix: (1) lookup changed to `spec.name == command_name || spec.aliases.contains(&command_name)`; (2) capture `canonical_name = slash_command.name`; (3) guidance strings updated to reference canonical form in remediation (e.g., `claw yes → /approve`, `claw n → /deny`, `claw skill → /skills`). 36 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint on `bf212b98`, 2026-05-27.
773. **Config deprecation warnings only emitted as unstructured stderr text in `--output-format json` mode** — dogfooded 2026-05-27 on `212f0b2a`. `emit_config_warning_once()` always wrote to stderr regardless of output format, causing JSON-mode callers to receive an unexpected `warning: ...` text line on stderr before the JSON object. Callers had to implement ad-hoc stripping. Fix: added `ConfigLoader::load_collecting_warnings()` method that returns `(RuntimeConfig, Vec<String>)` so callers can surface warnings structurally; `render_config_json()` now uses this and includes a `warnings: []` array in the config JSON envelope. Existing `load()` path unchanged (still emits to stderr for text-mode callers). 36 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori startup-friction probe on `212f0b2a`, 2026-05-27.
774. **`claw agents bogus`, `claw plugins bogus`, `claw mcp bogus` returned `hint: null`** — dogfooded 2026-05-27 on `727a1ea4`. Three "unknown subcommand" envelopes had `error_kind` correctly set but `hint: null`: (1) `unknown_agents_subcommand` — both text and JSON handler emitted single-line error with inline remediation after `.`, no `\n`; (2) `unknown_plugins_action` — same, period-delimited remediation; (3) `unknown_mcp_action``render_mcp_usage_json` never included a `hint` field at all. Fixes: (1)+(2) added `\n` before remediation suffix in `commands/src/lib.rs`; (3) added `hint` field to `render_mcp_usage_json` pointing at supported actions. All three now return non-null `hint`. 36 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori envelope-consistency probe on `727a1ea4`, 2026-05-27.
775. **Missing integration tests for #769-#771 interactive-only guards and #774 hint fields** — dogfooded 2026-05-27 on `c760a49c`. Fixes #769-#771 (session/cost/clear/memory/ultraplan/model/usage/stats/fork interactive-only guards) and #774 (agents/plugins/mcp unknown-subcommand hints) had no integration tests — a regression in any of those 10+ match arms would go undetected. Also: classify_error_kind unit test for `unknown_agents_subcommand` used the old single-line format string, not the `\n`-delimited format emitted after #774. Fixed: (1) updated unit test string to match new `\n`-delimited emission; (2) added `agents_plugins_mcp_unknown_subcommand_have_hint_774` asserting `error_kind` + non-null `hint` for all three; (3) added `interactive_only_guard_batch_769_to_771` asserting `interactive_only` + non-null `hint` for 10 cases. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori test-coverage sweep on `c760a49c`, 2026-05-27.
776. **Resume-mode JSON errors had opaque `error_kind:"resume_command_error"` + `hint:null`** — dogfooded 2026-05-27 on `028998d0` (pinpoint identified by Gaebal-gajae). `run_resume_command` returned errors (e.g. from `parse_history_count`) with hardcoded `error_kind:"resume_command_error"` and the full error string in `error` with no hint extraction. Wrappers had to regex prose instead of switching on typed fields. Three co-located gaps fixed: (1) `resume_session` JSON error path now applies `classify_error_kind` + `split_error_hint` so errors get specific `error_kind` (e.g. `invalid_history_count`) and non-null `hint`; (2) `parse_history_count` errors now use `invalid_history_count:` prefix + `\n` usage hint; (3) `/session exists|delete|switch|fork` missing-arg and unsupported-action errors now use `\n`-delimited format with `unsupported_resumed_command:` prefix. Existing test updated to match new error message format. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `028998d0`, 2026-05-27.
777. **Resumed `/plugins install|enable|disable|uninstall|update` returned opaque error_kind instead of interactive_only** — dogfooded 2026-05-27 on `2684737d` (pinpoint by Gaebal-gajae). The mutation arm in `run_resume_command` returned a bare single-line error; after #776 it was classified/split by the caller but fell to `error_kind:"unknown"` + `hint:null` because there was no `interactive_only:` prefix. Orchestrators had no stable signal to distinguish "command rejected — switch to REPL" from a transient error. Fix: each mutation verb now returns `interactive_only: /plugins {action} requires a live session...\n...hint...` so the caller emits `error_kind:"interactive_only"` + non-null hint pointing at REPL or direct CLI. Integration test `resume_plugin_mutations_are_typed_interactive_only_777` covers all 5 mutation verbs. 39 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `2684737d`, 2026-05-27.
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<String>` 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 <skill>` 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.
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.
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.
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.
785. **`claw dump-manifests --manifests-dir` missing-value errors were untyped/no-hint** — dogfooded 2026-05-27 on `e628b4bb`. `claw dump-manifests --manifests-dir --output-format json` and `claw dump-manifests --manifests-dir= --output-format json` emitted plain `--manifests-dir requires a path`; both classified as `error_kind:"unknown"` with `hint:null`. Manifest dumping is an automation/bootstrap surface, so callers need a stable `missing_flag_value` instead of regexing prose. Fix: changed both missing path branches to `missing_flag_value:` + newline usage hint. Regression guard: `dump_manifests_missing_dir_has_typed_kind_and_hint_785`. [SCOPE: claw-code] Source: gaebal-gajae manifest-bootstrap dogfood probe on `e628b4bb`, 2026-05-27.
+11 -3
View File
@@ -273,7 +273,10 @@ impl Display for ApiError {
}
}
if let Some(hint) = hint {
write!(f, " — hint: {hint}")?;
// #754: newline-delimited so split_error_hint() can extract the hint
// into the JSON envelope's `hint` field. The em-dash form was a
// single-line string that left hint:null in --output-format json.
write!(f, "\n{hint}")?;
}
Ok(())
}
@@ -608,11 +611,16 @@ mod tests {
rendered.starts_with("missing Anthropic credentials;"),
"hint should be appended, not replace the base message: {rendered}"
);
let hint_marker = " — hint: I see OPENAI_API_KEY is set — if you meant to use the OpenAI-compat provider, prefix your model name with `openai/` so prefix routing selects it.";
// #754: hint is now newline-delimited so split_error_hint() can extract it
let hint_text = "I see OPENAI_API_KEY is set — if you meant to use the OpenAI-compat provider, prefix your model name with `openai/` so prefix routing selects it.";
assert!(
rendered.ends_with(hint_marker),
rendered.ends_with(hint_text),
"rendered error should end with the hint: {rendered}"
);
assert!(
rendered.contains('\n'),
"rendered error must contain newline separator so split_error_hint works: {rendered}"
);
// Classification semantics are unaffected by the presence of a hint.
assert_eq!(error.safe_failure_class(), "provider_auth");
assert!(!error.is_retryable());
+6 -1
View File
@@ -1649,10 +1649,15 @@ NO_EQUALS_LINE
rendered.starts_with("missing Anthropic credentials;"),
"canonical base message should still lead the rendered error: {rendered}"
);
// #754: hint delimiter changed from " — hint: " to "\n" so split_error_hint works
assert!(
rendered.contains(" — hint: I see OPENAI_API_KEY is set"),
rendered.contains("I see OPENAI_API_KEY is set"),
"rendered error should carry the env-driven hint: {rendered}"
);
assert!(
rendered.contains('\n'),
"rendered error must use newline separator (#754): {rendered}"
);
}
#[test]
+31 -3
View File
@@ -2334,8 +2334,15 @@ pub fn handle_plugins_slash_command(
reload_runtime: false,
})
}
// #743/#420: "help" was caught by Some(other) → unknown_plugins_action error with hint:null.
// agents/mcp/skills all return a help envelope; plugins must match that parity.
Some("help" | "-h" | "--help") => Ok(PluginsCommandResult {
message: "Plugins\n Usage /plugins [list|show <id>|install <id>|enable <id>|disable <id>|uninstall <id>|update <id>|help]\n Subcommands list show install enable disable uninstall update help"
.to_string(),
reload_runtime: false,
}),
Some(other) => Err(PluginError::CommandFailed(format!(
"unknown_plugins_action: '{other}' is not a supported /plugins action. Use list, show, install, enable, disable, uninstall, or update."
"unknown_plugins_action: '{other}' is not a supported /plugins action.\nUse: list, show, install, enable, disable, uninstall, or update."
))),
}
}
@@ -2399,7 +2406,7 @@ pub fn handle_agents_slash_command(args: Option<&str>, cwd: &Path) -> std::io::R
Some(args) if is_help_arg(args) => Ok(render_agents_usage(None)),
Some(args) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("unknown agents subcommand: {args}. Supported: list, show, help"),
format!("unknown agents subcommand: {args}.\nSupported: list, show, help"),
)),
}
}
@@ -2461,6 +2468,8 @@ pub fn handle_agents_slash_command_json(args: Option<&str>, cwd: &Path) -> std::
"requested": name,
// #734: parity with skills show which always emits a message field
"message": format!("agent '{}' not found", name),
// #760: hint so callers know how to enumerate available agents
"hint": "Run `claw agents list` to see available agents.",
}));
}
Ok(render_agents_report_json_with_action(cwd, &matched, "show"))
@@ -2468,7 +2477,7 @@ pub fn handle_agents_slash_command_json(args: Option<&str>, cwd: &Path) -> std::
Some(args) if is_help_arg(args) => Ok(render_agents_usage_json(None)),
Some(args) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("unknown agents subcommand: {args}. Supported: list, show, help"),
format!("unknown agents subcommand: {args}.\nSupported: list, show, help"),
)),
}
}
@@ -2612,6 +2621,8 @@ pub fn handle_skills_slash_command_json(args: Option<&str>, cwd: &Path) -> std::
"error_kind": "skill_not_found",
"message": format!("skill '{}' not found", name),
"requested": name,
// #761: hint so callers know how to enumerate available skills
"hint": "Run `claw skills list` to see available skills.",
}));
}
Ok(render_skills_report_json_with_action(&matched, "show"))
@@ -4047,6 +4058,8 @@ fn render_mcp_server_report_json(
"found": false,
"server_name": server_name,
"message": format!("server `{server_name}` is not configured"),
// #761: hint so callers know how to enumerate configured MCP servers
"hint": "Run `claw mcp list` to see configured servers.",
}),
}
}
@@ -4154,11 +4167,26 @@ fn render_mcp_usage(unexpected: Option<&str>) -> String {
}
fn render_mcp_usage_json(unexpected: Option<&str>) -> Value {
// #748: add error_kind when unexpected is set, matching agents/plugins unknown-subcommand shape.
let error_kind: Value = if unexpected.is_some() {
json!("unknown_mcp_action")
} else {
Value::Null
};
// #774: add hint field so unknown_mcp_action errors have non-null hint parity
// with agents/plugins unknown-subcommand envelopes.
let hint: Value = if unexpected.is_some() {
json!("Use: list, show <server>, or help")
} else {
Value::Null
};
json!({
"kind": "mcp",
"action": "help",
"ok": unexpected.is_none(),
"status": if unexpected.is_some() { "error" } else { "ok" },
"error_kind": error_kind,
"hint": hint,
"usage": {
"slash_command": "/mcp [list|show <server>|help]",
"direct_cli": "claw mcp [list|show <server>|help]",
+68 -1
View File
@@ -217,7 +217,10 @@ impl Display for ConfigError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(error) => write!(f, "{error}"),
Self::Parse(error) => write!(f, "{error}"),
Self::Parse(error) => write!(
f,
"{error}\nFix: open the file shown above and correct the JSON syntax, then retry."
),
}
}
}
@@ -343,6 +346,70 @@ impl ConfigLoader {
feature_config,
})
}
/// Like [`load`] but also returns the list of validation warnings collected during
/// loading, without emitting them to stderr. Callers that want to surface warnings
/// through a structured channel (e.g. the JSON config envelope) should use this.
/// #773: enables JSON-mode callers to include `warnings` in their output envelope
/// instead of receiving unstructured text on stderr.
pub fn load_collecting_warnings(&self) -> Result<(RuntimeConfig, Vec<String>), ConfigError> {
let mut merged = BTreeMap::new();
let mut loaded_entries = Vec::new();
let mut mcp_servers = BTreeMap::new();
let mut all_warnings: Vec<String> = Vec::new();
for entry in self.discover() {
crate::config_validate::check_unsupported_format(&entry.path)?;
let Some(parsed) = read_optional_json_object(&entry.path)? else {
continue;
};
let validation = crate::config_validate::validate_config_file(
&parsed.object,
&parsed.source,
&entry.path,
);
if !validation.is_ok() {
let first_error = &validation.errors[0];
return Err(ConfigError::Parse(first_error.to_string()));
}
all_warnings.extend(validation.warnings.iter().map(|w| w.to_string()));
validate_optional_hooks_config(&parsed.object, &entry.path)?;
merge_mcp_servers(&mut mcp_servers, entry.source, &parsed.object, &entry.path)?;
deep_merge_objects(&mut merged, &parsed.object);
loaded_entries.push(entry);
}
// Still emit to stderr for non-JSON callers that go through the normal load() path;
// here we just *also* return them so callers can surface them structurally.
for warning in &all_warnings {
emit_config_warning_once(warning);
}
let merged_value = JsonValue::Object(merged.clone());
let feature_config = RuntimeFeatureConfig {
hooks: parse_optional_hooks_config(&merged_value)?,
plugins: parse_optional_plugin_config(&merged_value)?,
mcp: McpConfigCollection {
servers: mcp_servers,
},
oauth: parse_optional_oauth_config(&merged_value, "merged settings.oauth")?,
model: parse_optional_model(&merged_value),
aliases: parse_optional_aliases(&merged_value)?,
permission_mode: parse_optional_permission_mode(&merged_value)?,
permission_rules: parse_optional_permission_rules(&merged_value)?,
sandbox: parse_optional_sandbox_config(&merged_value)?,
provider_fallbacks: parse_optional_provider_fallbacks(&merged_value)?,
trusted_roots: parse_optional_trusted_roots(&merged_value)?,
};
let config = RuntimeConfig {
merged,
loaded_entries,
feature_config,
};
Ok((config, all_warnings))
}
}
impl RuntimeConfig {
File diff suppressed because it is too large Load Diff
@@ -283,6 +283,16 @@ fn compact_subcommand_json_help_fails_fast_when_stdin_closed() {
.contains("claw compact"),
"message should name compact: {parsed}"
);
// #749: hint must be non-empty (was null before fix — same class as #738/#745/#746)
let hint = parsed["hint"].as_str().unwrap_or("");
assert!(
!hint.is_empty(),
"compact interactive-only JSON must have non-empty hint (#749); got: {parsed}"
);
assert!(
hint.contains("/compact") || hint.contains("--resume"),
"hint should mention /compact or --resume: {hint}"
);
fs::remove_dir_all(&workspace).expect("workspace cleanup should succeed");
}
File diff suppressed because it is too large Load Diff