feat: #144 phase 1 + ROADMAP filing — claw mcp degrades gracefully on malformed config

Filing + Phase 1 fix in one commit (sibling of #143).

## Context

With #143 Phase 1 landed (`claw status` degrades), `claw mcp` was the
remaining diagnostic surface that hard-failed on a malformed `.claw.json`.
Same input, same parse error, same partial-success violation. Fresh
dogfood at 18:59 KST caught it on main HEAD `e2a43fc`.

## Changes

### ROADMAP.md
Added Pinpoint #144 documenting the gap and acceptance criteria. Joins
the partial-success / Principle #5 cluster with #143.

### rust/crates/commands/src/lib.rs
`render_mcp_report_for()` + `render_mcp_report_json_for()` now catch the
ConfigError at loader.load() instead of propagating:

- **Text mode** prepends a "Config load error" block (same shape as
  #143's status output) before the MCP listing. The listing still renders
  with empty servers so the output structure is preserved.
- **JSON mode** adds top-level `status: "ok" | "degraded"` +
  `config_load_error: string | null` fields alongside existing fields
  (`kind`, `action`, `working_directory`, `configured_servers`,
  `servers[]`). On clean runs, `status: "ok"` and
  `config_load_error: null`. On parse failure, `status: "degraded"`,
  `config_load_error: "..."`, `servers: []`, exit 0.
- Both list and show actions get the same treatment.

### Regression test
`commands::tests::mcp_degrades_gracefully_on_malformed_mcp_config_144`:
- Injects the same malformed .claw.json as #143 (one valid + one broken
  mcpServers entry).
- Asserts mcp list returns Ok (not Err).
- Asserts top-level status: "degraded" and config_load_error names the
  malformed field path.
- Asserts show action also degrades.
- Asserts clean path returns status: "ok" with config_load_error null.

## Live verification

$ claw mcp --output-format json
{
  "action": "list",
  "kind": "mcp",
  "status": "degraded",
  "config_load_error": ".../.claw.json: mcpServers.missing-command: missing string field command",
  "working_directory": "/Users/yeongyu/clawd",
  "configured_servers": 0,
  "servers": []
}
Exit 0.

## Contract alignment after this commit

All three diagnostic surfaces match now:
- `doctor` — degraded envelope with typed check entries 
- `status` — degraded envelope with config_load_error  (#143)
- `mcp` — degraded envelope with config_load_error  (this commit)

Phase 2 (typed-error object joining taxonomy §4.44) tracked separately
across all three surfaces.

Full workspace test green except pre-existing resume_latest flake (unrelated).

Closes ROADMAP #144 phase 1.
This commit is contained in:
YeonGyu-Kim
2026-04-21 19:07:17 +09:00
parent e2a43fcd49
commit faeaa1d30c
2 changed files with 222 additions and 22 deletions

View File

@@ -5455,3 +5455,68 @@ Doctor keeps going and produces a full typed report. Status refuses to produce a
**Blocker.** None for Phase 1. Phase 2 depends on the typed-error taxonomy landing (ROADMAP §4.44), but Phase 1 can ship independently and be tightened later.
**Source.** Jobdori dogfood 2026-04-21 18:30 KST on main HEAD `e73b6a2`, surfaced by running `claw status` in `/Users/yeongyu/clawd` which contains a `.claw.json` with deliberately broken MCP entries. Joins **partial-success / degraded-mode** cluster (Principle #5, Phase 6) and **surface consistency** cluster (#141 help-contract unification, #108 typo guard). Session tally: ROADMAP #143.
## Pinpoint #144. `claw mcp` hard-fails on malformed MCP config — same surface inconsistency as #143, one command over
**Gap.** With `claw status` fixed in #143 Phase 1, `claw mcp` is now the remaining diagnostic surface that hard-fails on a malformed `.claw.json`. Same input, same parse error, same partial-success violation.
**Verified on main HEAD `e2a43fc` (2026-04-21 18:59 KST):**
Same `.claw.json` used for #143 repro (one valid `everything` server + one malformed `missing-command` entry).
`claw mcp`:
```
error: /Users/.../.claw.json: mcpServers.missing-command: missing string field command
Run `claw --help` for usage.
```
Exit 1. No list. The well-formed `everything` server is invisible.
`claw mcp --output-format json`:
```json
{"error":"/Users/.../.claw.json: mcpServers.missing-command: missing string field command","type":"error"}
```
Exit 1. Same story.
`claw status --output-format json` on the same file (post-#143):
```json
{"kind":"status","status":"degraded","config_load_error":"...","workspace":{...},"sandbox":{...},...}
```
Exit 0. Full envelope with error surfaced.
**Why this is a clawability gap (same family as #143).**
1. **Principle #5 violation**: partial success is first-class. One malformed entry shouldn't make the entire MCP subsystem invisible.
2. **Surface inconsistency (cluster of 3)**: after #143 Phase 1, the behavior matrix is:
- `doctor` — degraded envelope ✅
- `status` — degraded envelope ✅ (#143)
- `mcp` — hard-fail ❌ (this pinpoint)
3. **Clawhip impact**: `claw mcp --output-format json` is used by orchestrators to detect which MCP servers are available before invoking tools. A broken probe forces clawhip to fall back to doctor parse, which is suboptimal.
**Fix shape (~40 lines, mirrors #143 Phase 1).**
1. Make `render_mcp_report_json_for()` and `render_mcp_report_for()` catch the `ConfigError` at `loader.load()?`.
2. On parse failure, emit a degraded envelope:
```json
{
"kind": "mcp",
"action": "list",
"status": "degraded",
"config_load_error": "...",
"working_directory": "...",
"configured_servers": 0,
"servers": []
}
```
3. Text mode: prepend a "Config load error" block (same shape as #143) before the "MCP" block.
4. Exit 0 so downstream probes don't treat a parse error as process death.
**Acceptance.**
- `claw mcp` and `claw mcp --output-format json` on a workspace with malformed config exit 0.
- JSON mode includes `status: "degraded"` and `config_load_error` field.
- Text mode shows the parse error in a separate block, not as the only output.
- Clean path (no config errors) still returns `status: "ok"` (or equivalent — align with #143 serializer).
- Regression test: inject malformed config, assert mcp returns degraded envelope.
**Blocker.** None. Mirrors #143 Phase 1 shape exactly.
**Future phase (joins #143 Phase 2).** When typed-error taxonomy lands (§4.44), promote `config_load_error` from string to typed object across `doctor`, `status`, and `mcp` in one pass.
**Source.** Jobdori dogfood 2026-04-21 18:59 KST on main HEAD `e2a43fc`. Joins **partial-success** cluster (#143, Principle #5) and **surface consistency** cluster. Session tally: ROADMAP #144.