Pinpoint #172: SCHEMAS.md v1.5 Emission Baseline documentation inaccuracy
discovered during cycle #98 probe.
The Phase 1 normalization targets section claimed:
"unify where `action` field appears (only in 4 inventory verbs)"
But reality is only 3 inventory verbs have `action`:
- mcp
- skills
- agents
list-sessions uses `command` instead (the documented 1-of-13 deviation
already captured elsewhere in v1.5 baseline).
This is a doc-truthfulness issue (same family as cycles #76, #79, #82).
Active misdocumentation leads downstream consumers to assume 4-verb
coverage when building adapters/dispatchers.
Changes:
1. SCHEMAS.md: 'only in 4 inventory verbs' → 'only in 3 inventory verbs: mcp, skills, agents'
2. Added regression test `v1_5_action_field_appears_only_in_3_inventory_verbs_172`
- Asserts mcp/skills/agents HAVE action field
- Asserts help/version/doctor/status/sandbox/system-prompt/bootstrap-plan/list-sessions do NOT have action field
- Forces SCHEMAS.md + binary to stay synchronized
Test added:
- `v1_5_action_field_appears_only_in_3_inventory_verbs_172` (8 negative cases + 3 positive cases)
Tests: 227/227 pass (+1 from #172).
Related: #155 (doc parity family), #168c (emission baseline).
Doc-truthfulness family: #76, #79, #82, #172.
Pinpoint #171: typed-error classifier gap discovered during #141 probe cycle #97.
`claw list-sessions --help` emits:
error: unexpected extra arguments after `claw list-sessions`: --help
This format is used by multiple verbs that reject trailing positional args:
- list-sessions
- plugins (subcommands)
- config (subcommands)
- diff
- load-session
Before fix:
{"error": "unexpected extra arguments after `claw list-sessions`: --help",
"hint": null,
"kind": "unknown",
"type": "error"}
After fix:
{"error": "unexpected extra arguments after `claw list-sessions`: --help",
"hint": "Run `claw --help` for usage.",
"kind": "cli_parse",
"type": "error"}
The pattern `unexpected extra arguments after \`claw` is specific enough
that it won't hijack generic prose mentioning "unexpected extra arguments"
in other contexts (sanity test included).
Side benefit: like #169/#170, correctly classified cli_parse errors now
auto-trigger the #247 hint synthesizer.
Related #141 gap not yet closed: `claw list-sessions --help` still errors
instead of showing help (requires separate parser fix to recognize --help
as a distinct path). This classifier fix at least makes the error surface
typed correctly so consumers can distinguish "parse failure" from "unknown"
and potentially retry without the --help flag.
Test added:
- `classify_error_kind_covers_unexpected_extra_args_171` (4 positive cases
+ 1 sanity guard)
Tests: 226/226 pass (+1 from #171).
Typed-error family: #121, #127, #129, #130, #164, #169, #170, #247.
Cycle #96 dogfood found practical install-experience gap in USAGE.md.
#153 closed by commit 6212f17 (same branch, same cycle).
Part of discoverability family (#155, help/USAGE parity).
Pinpoint count: 62 filed, 51 genuinely-open + #153 closed this cycle.
Pinpoint #153 closure. USAGE.md was missing practical instructions for:
1. Adding the claw binary to PATH (symlink vs export PATH)
2. Verifying the install works (version, doctor, --help)
3. Troubleshooting PATH issues (which, echo $PATH, ls -la)
New subsections:
- "Add binary to PATH" with two common options
- "Verify install" with post-install health checks
- Troubleshooting guide for common failures
Target audience: developers building from source who want to run `claw`
from any directory without typing `./rust/target/debug/claw`.
Discovered during cycle #96 dogfood (10-min reminder cycle).
Tests: 225/225 still pass (doc-only change).
Pinpoint #170: Extended typed-error classifier coverage gap discovered during
dogfood probe 2026-04-23 07:30 Seoul (cycle #95).
The #169 comment claimed to cover `--permission-mode bogus` via the
`unsupported value for --` pattern, but the actual `parse_permission_mode_arg`
message format is `unsupported permission mode 'bogus'` (NO `for --` prefix).
Doc-vs-reality lie in the #169 fix itself — fixed here.
Four classifier gaps closed:
1. `unsupported permission mode '<value>'` → cli_parse
(from: `parse_permission_mode_arg`)
2. `invalid value for --reasoning-effort: '<value>'; must be ...` → cli_parse
(from: `--reasoning-effort` validator)
3. `model string cannot be empty` → cli_parse
(from: empty --model rejection)
4. `slash command /<name> is interactive-only. Start \`claw\` ...` →
slash_command_requires_repl (NEW kind — more specific than cli_parse)
The fourth pattern gets its own kind (`slash_command_requires_repl`) because
it's a command-mode misuse, not a parse error. Downstream consumers can
programmatically offer REPL-launch guidance.
Side benefit: like #169, the correctly classified cli_parse errors now
auto-trigger the #247 hint synthesizer ("Run `claw --help` for usage.").
Test added:
- `classify_error_kind_covers_flag_value_parse_errors_170_extended`
(4 positive cases + 2 sanity guards)
Tests: 225/225 pass (+1 from #170).
Typed-error family: #121, #127, #129, #130, #164, #169, #247.
Discovered via systematic probe angle: 'error message pattern audit' \u2014
grep each error emission for pattern, confirm classifier matches.
Pinpoint #169: typed-error classifier gap discovered during dogfood probe.
`claw --output-format json --output-format xml doctor` was emitting:
{"error": "unsupported value for --output-format: xml ...",
"hint": null,
"kind": "unknown",
"type": "error"}
After fix:
{"error": "unsupported value for --output-format: xml ...",
"hint": "Run `claw --help` for usage.",
"kind": "cli_parse",
"type": "error"}
The change adds two new classifier branches to `classify_error_kind`:
1. `unsupported value for --` → cli_parse
2. `missing value for --` → cli_parse
Covers all `CliOutputFormat::parse` / `parse_permission_mode_arg` rejections
and any future flag-value validation messages using the same pattern.
Side benefit: the #247 hint synthesizer ("Run `claw --help` for usage.")
now triggers automatically because the error is now correctly classified
as cli_parse. Consumers get both correct kind AND helpful hint.
Test added:
- `classify_error_kind_covers_flag_value_parse_errors_169` (4 positive +
1 sanity case)
Tests: 224/224 pass (+1 from #169).
Discovered during dogfood probe 2026-04-23 07:00 Seoul, cycle #94.
Refs: #169, typed-error family (#121, #127, #129, #130, #164, #247)
Pinpoint #155: USAGE.md was missing documentation for three interactive
commands that appear in `claw --help`:
- /ultraplan [task]
- /teleport <symbol-or-path>
- /bughunter [scope]
Also adds full documentation for other underdocumented commands:
- /commit, /pr, /issue, /diff, /plugin, /agents
Converts inline sentence list into structured section 'Interactive slash
commands (inside the REPL)' with brief descriptions for each command.
Closes#155 gap: discovered during dogfood probing of help/USAGE parity.
No code changes. Pure documentation update.
Phase 0 Task 4 of the JSON Productization Program: CI shape parity guard.
This test locks the v1.5 emission baseline (documented in SCHEMAS.md § v1.5
Emission Baseline) so any future PR that introduces shape drift in a documented
verb fails this test at PR time.
Complements Task 2 (no-silent guarantee) by asserting SPECIFIC top-level key
sets, not just 'stdout is non-empty valid JSON'. If a verb adds/removes a
top-level field, this test fails with a clear error message pointing to
SCHEMAS.md § v1.5 Emission Baseline for update guidance.
Coverage:
- 8 success-path verbs with locked shape (help, version, doctor, skills,
agents, system-prompt, bootstrap-plan, list-sessions)
- 2 error-path cases with locked error envelope shape (prompt-no-arg, doctor --foo)
Key enforcement rules:
- Success envelope: exact key set match per verb
- Error envelope: {error, hint, kind, type} (4 keys, all verbs)
- list-sessions deliberately kept as {command, sessions} (Phase 1 target)
Test design intent:
- Locks CURRENT (possibly imperfect) shape, NOT target shape
- Forces PR authors to update both code + SCHEMAS.md + test together
- Makes Phase 1 shape normalization PRs visible: 'update this test'
Phase 0 now COMPLETE:
- Task 1 ✅ Stream routing fix (cycle #89)
- Task 2 ✅ No-silent guarantee (cycle #90)
- Task 3 ✅ Per-verb emission inventory SCHEMAS.md (cycle #91)
- Task 4 ✅ CI shape parity guard (this cycle)
Tests: 18 output_format_contract tests all pass (+1 from Task 4).
v1.5 emission baseline now locked by code + tests + docs.
Refs: #168c, cycle #92, Phase 0 Task 4 (final)
Under --output-format json, error envelopes were emitted to stderr via
eprintln!. This violated the emission contract: stdout should carry the
contractual envelope (success OR error); stderr is reserved for
non-contractual diagnostics.
Cycle #87 controlled matrix audit found bootstrap/dump-manifests/state
exhibited this pattern (exit 1, stdout 0 bytes, stderr N bytes under
--output-format json).
Fix: change eprintln! to println! for the JSON error envelope path in main().
Text mode continues to route errors to stderr (conventional).
Verification:
- bootstrap --output-format json: stdout now carries envelope, exit 1
- dump-manifests --output-format json: stdout now carries envelope, exit 1
- Text mode: errors still on stderr with [error-kind: ...] prefix (no regression)
Tests:
- Updated assert_json_error_envelope helper to read from stdout (was stderr)
- Added error_envelope_emitted_to_stdout_under_output_format_json_168c
regression test that asserts envelope on stdout + non-JSON on stderr
- All 16 output_format_contract tests pass
Phase 0 Task 1 complete: emission routing fixed across all error-path verbs.
Phase 0 Task 2 (no-silent CI guarantee) remains.
Refs: #168c (cycle #87 filing), cycle #88 emission contract framing
Fresh-dogfood validation (cycle #84, #168) proved the original locus premise was
underspecified. v1.0 was never a coherent contract — each verb has a bespoke JSON
shape with no coordination, and bootstrap JSON is completely broken (silent
failure, exit 0 no output).
Revised migration plan:
- Phase 0 (NEW): Emergency fix for silent failures (#168 bootstrap JSON)
- Phase 1 (NEW): v1.5 baseline — minimal JSON invariants across all 14 verbs
- Every command emits valid JSON with --output-format json
- Every command has top-level 'kind' field for verb ID
- Every error envelope follows {error, hint, kind, type}
- Phase 2 (renamed from Phase 1): v2.0 wrapped envelope (opt-in)
- Phase 3 (renamed from Phase 2): v2.0 default
- Phase 4 (renamed from Phase 3): v1.0/v1.5 deprecation
Rationale:
- Can't migrate from 'incoherent' to 'coherent v2.0' in one jump
- Consumers need stable target (v1.5) to transition from
- Silent failures must be fixed BEFORE migration (consumers can't detect breakage)
Effort revision: ~9 dev-days (Phase 0: 1 + Phase 1: 3 + Phase 2: 5) vs original
~6 dev-days for direct v1.0→v2.0 (which would have failed).
Doctrine implication: Fresh-dogfood principle (#9, cycle #73) prevented a multi-day
migration from hitting an unsolvable baseline problem. Evidence-backed mid-design
correction.
Fresh dogfood validation (cycle #84) revealed the binary v1.0 envelope is NOT
consistent across commands:
- list-sessions: {command, sessions}
- doctor: {checks, kind, message, ...}
- bootstrap: (no JSON output at all)
- mcp: {action, kind, status, ...}
Each command has a custom JSON shape. Bootstrap's JSON path is completely broken
(exit 0 but no output). This is not 'v1.0 vs v2.0 design difference' — it's
'no consistent v1.0 ever existed'.
This explains why #164 (envelope migration) is blocked on design: the 'v1.0 from'
was never coherent. The real task is not 'migrate v1.0 to v2.0' but 'migrate
incoherent-per-command shapes to coherent-common-envelope'.
Implications for cycles #76–#82: The P0 doc fixes were correct to mark SCHEMAS.md
as 'aspirational' because the binary never had a consistent contract to document.
The deeper issue: each verb renderer was written independently with no envelope
coordination.
Three options proposed:
- A: accept per-command shapes (status quo + documentation)
- B: enforce common wrapper (FIX_LOCUS_164 full approach)
- C: hybrid (document current incoherence, then migrate 3 pilot verbs)
Recommendation: Option C. Documents truth immediately, enables phased migration.
This filing resolves the #164 design blocker: now we understand what we're
migrating from.
SCHEMAS.md locks JSON envelope contract for all 14 clawable commands.
No corresponding contract for text output (--output-format text).
Text output is ad hoc per-command: no documented format, no column ordering
guarantee, no stability contract. Claws parsing text output have no safety.
Filed as discovery gap from systematic doc audit (cycle #83). Design options:
- Option A: Document text contracts (parallel to JSON) — 4 dev-days
- Option B: Declare text unstable, point to JSON — 1 dev-day (recommended)
- Option C: Defer until post-#164 JSON migration
Related to #164 (JSON migration) and #250 (surface parity audit).
The aspirational SCHEMAS.md doc (v2.0 target) was the source of truth misdocumentation.
Three downstream docs (USAGE, ERROR_HANDLING, CLAUDE) inherited the false claim that
v1.0 binary emits common fields it doesn't actually emit.
Fixing SCHEMAS.md at the source eliminates the root cause for all four P0 instances.
Doc-truthfulness P0 family now complete: 4/4 closed, root cause identified + fixed.
All fixes shipped within 6 cycles (#76 audit → #82 execution).
SCHEMAS.md was presenting the target v2.0 schema as the current binary contract.
This is the source of truth document, so the misdocumentation propagated to every
downstream doc (USAGE.md, ERROR_HANDLING.md, CLAUDE.md all inherited the false
premise that v1.0 includes timestamp/command/exit_code/etc).
Fixed with:
1. CRITICAL header at top: marks entire doc as v2.0 target, not v1.0 reality
2. 'TARGET v2.0 SCHEMA' headers on Common Fields section
3. Comprehensive Appendix: v1.0 actual shape + migration timeline + v1.0 code example
4. Links to FIX_LOCUS_164.md + ERROR_HANDLING.md for v1.0 reality
5. FAQ: clarifies the version mismatch and when v2.0 ships
This closes the fourth P0 doc-truthfulness instance (4/4 in family):
- #78 USAGE.md: active misdocumentation (fixed#78)
- #79 ERROR_HANDLING.md: copy-paste trap (fixed#79)
- #165 CLAUDE.md: boundary collapse (fixed#81)
- #166 SCHEMAS.md: aspirational source doc (fixed#82)
Pattern is now crystallized: SCHEMAS.md was the aspirational source;
three downstream docs (USAGE, ERROR_HANDLING, CLAUDE) inherited the false v2.0-as-v1.0
claim. Fix the source (SCHEMAS.md), which eliminates the root cause for all four.
CLAUDE.md Option A implemented. P0 doc-truthfulness family now at 3 closed +
0 open (all 3 fixed within the same dogfood session).
Taxonomy refinement added: P0 doc-truthfulness has three distinct subclasses:
- active misdocumentation (false sentence) — USAGE.md cycle #78
- copy-paste trap (broken example code) — ERROR_HANDLING.md cycle #79
- target/current boundary collapse (v2.0 as v1.0) — CLAUDE.md cycle #81
All three related to #164 (envelope divergence). Root cause consistent across
family; remedies differ per subclass.
CLAUDE.md was documenting the v2.0 target schema as if it were current binary
behavior. This misled validator/harness implementers into assuming the Rust
binary emits timestamp, command, exit_code, output_format, schema_version fields
when it doesn't.
Fixed by explicitly marking the boundary:
1. SCHEMAS.md section: now clearly labels 'target v2.0 design' and lists both
v1.0 (actual binary) and v2.0 (target) field shapes
2. Clawable commands requirements: now explicitly separates v1.0 (current) and
v2.0 (post-FIX_LOCUS_164) envelope requirements
3. Added inline migration note pointing to FIX_LOCUS_164.md
This closes#165 as the third P0 doc-truthfulness fix (Option A: preserve current
truth, add v2.0 target as separate labeled section).
P0 doc-truthfulness family pattern (all three related to #164 envelope divergence):
- #78 USAGE.md: active misdocumentation (fixed cycle #78)
- #79 ERROR_HANDLING.md: copy-paste trap (fixed cycle #79)
- #165 CLAUDE.md: target/current boundary collapse (fixed cycle #81)
CLAUDE.md claims 'Common fields (all envelopes): timestamp, command, exit_code,
output_format, schema_version' but the actual binary v1.0 doesn't emit these.
This is aspirational (v2.0 target from SCHEMAS.md) documented as current behavior
in a file that's supposed to describe the Python reference harness.
Filed as 3rd member of doc-truthfulness P0 family (joins #78, #79).
Both options documented: update CLAUDE.md for v1.0 OR clarify it's v2.0 aspirational.
Recommendation: Option A (keep CLAUDE.md truthful about actual validation).
Part of broader #164 family (envelope schema divergence across all docs).
Formalizes a 4-level severity scale for documentation-vs-implementation divergence:
- P0: Active misdocumentation (consumer code breaks) — immediate fix
- P1: Stale docs (consumer confused) — high priority
- P2: Incomplete docs (friction, eventual success) — medium
- P3: Terminology drift (confusion but survivable) — low
Parallel to diagnostic-strictness scale (cycles #57–#69). Both are
'truth-over-convenience' constraints.
Evidence: cycles #78–#79 found 2 P0 instances in USAGE.md and ERROR_HANDLING.md,
both related to JSON envelope shape. Root cause: SCHEMAS.md is aspirational (v2.0),
binary still emits v1.0, docs needed to be empirical not aspirational.
Going forward: doc audits compare against actual binary, flag P0 violations
immediately, link forward to migration plans (FIX_LOCUS_164.md).
The Python code examples were accessing nested error.kind like envelope['error']['kind'],
but v1.0 emits flat envelopes with error as a STRING and kind at top-level.
Updated:
- Table header: now shows actual v1.0 shape {error: "...", kind: "...", type: "error"}
- match statement: switched from envelope.get('error',{}).get('kind') to envelope.get('kind')
- All ClawError raises: changed from envelope['error']['message'] to envelope.get('error','')
because error field is a STRING in v1.0, not a nested object
- Added inline comments on every error case noting v1.0 vs v2.0 difference
- Appendix: split into v1.0 (actual/current) and v2.0 (target after FIX_LOCUS_164)
The code examples now work correctly against the actual binary.
This was active misdocumentation (P0 severity) — the Python examples would crash
if a consumer tried to use them.
The JSON output section was misleading — it claimed the binary emits
exit_code, command, timestamp, output_format, schema_version, and nested
error objects. The binary actually emits v1.0 flat shape (kind at top-level,
error as string, no common metadata fields).
Updated section:
- Documents actual v1.0 success and error envelope shapes
- Lists known issues (missing fields, overloaded kind, flat error)
- Shows how to dispatch on v1.0 (check type=='error' before reading kind)
- Warns users NOT to rely on kind alone
- Links to FIX_LOCUS_164.md for migration plan
- Explains Phase 1/2/3 timeline for v2.0 adoption
This is a doc-only fix that makes USAGE.md truthful about the current behavior
while preparing users for the coming schema migration.
Binary emits different envelope shape than SCHEMAS.md documents:
- Missing: timestamp, command, exit_code, output_format, schema_version
- Wrong placement: kind is top-level, not nested under error
- Extra: type:error field not in schema
- Wrong type: error is string, not object with operation/target/retryable
Additional issue: 'kind' field is semantically overloaded (verb-id in
success envelopes, error-kind in error envelopes) — violates typed contract.
Filed as 7th member of typed-error family (joins #102, #121, #127, #129, #130, #245).
Recommended fix: Option A — update binary to match schema (principled design).
Attempted cherry-pick of #248 (1 commit) onto main. Encountered 2 conflict zones
in main.rs (test definitions + error classification). Manual regex cleanup left
orphaned diff markers that Rust compiler rejected.
Decision: Rebase-bridge works for 1-conflict branches, but 2+ conflicts in 12K+-line
files require author context. Revised strategy: push main to origin, request branch
authors rebase locally with IDE support, then merge from updated origin branches.
Estimated timeline: 30 min for branch authors to rebase 8 branches in parallel.
Fresh dogfood found no new pinpoints. All core verbs working correctly.
Blocker: 8 remaining review-ready branches on origin have conflicts with
cycle #72's 4 merges. Root cause: remote branches predated the merge chain.
Example: feat/jobdori-127-verb-suffix-flags rebase fails on commit 3/3
because cycle #72 added 15+ new LocalHelpTopic variants.
Recommend: coordinate with branch authors to rebase against new main.
Cycle #74 will post integration checkpoint + queue status.
Backlog-truthfulness (cycle #60) validated: fresh dogfood on current main confirmed
#163 was closed by cycle #72's help-parity chain merge. Zero duplicate work.
Cleanup: removed /tmp/jobdori-163 worktree and fix/jobdori-163-help-help-selfref branch.
Problem: In git worktrees, .git is a pointer file (not a directory), so cargo's
rerun-if-changed=.git/HEAD never triggers when commits are made. This causes
claw version to report a stale SHA after new commits.
Solution: Add resolve_git_head_path() helper that detects worktree mode:
- If .git is a file: parse gitdir pointer, watch <gitdir>/HEAD
- If .git is a directory: watch .git/HEAD (regular repo)
This ensures build.rs invalidates on each commit, making version output truthful.
Verification: Binary built in worktree now reports correct SHA after commits
(before: stale, after: current HEAD).
Relates to ROADMAP #161 (filed cycle #65, implemented cycle #69).
Diagnostic-strictness family member.
Diff: 21 lines added (resolve_git_head_path + conditional rerun-if-changed).
## What Was Broken (ROADMAP #130e, filed cycle #53)
Three subcommands leaked `missing_credentials` errors when called
with `--help`:
$ claw help --help
[error-kind: missing_credentials]
error: missing Anthropic credentials...
$ claw submit --help
[error-kind: missing_credentials]
error: missing Anthropic credentials...
$ claw resume --help
[error-kind: missing_credentials]
error: missing Anthropic credentials...
This is the same dispatch-order bug class as #251 (session verbs).
The parser fell through to the credential check before help-flag
resolution ran. Critical discoverability gap: users couldn't learn
what these commands do without valid credentials.
## Root Cause (Traced)
`parse_local_help_action()` (main.rs:1260) is called early in
`parse_args()` (main.rs:1002), BEFORE credential check. But the
match statement inside only recognized:
status, sandbox, doctor, acp, init, state, export, version,
system-prompt, dump-manifests, bootstrap-plan, diff, config.
`help`, `submit`, `resume` were NOT in the list, so the function
returned `None`, and parsing continued to credential check which
then failed.
## What This Fix Does
Same pattern as #130c (diff) and #130d (config):
1. **LocalHelpTopic enum extended** with Meta, Submit, Resume variants
2. **parse_local_help_action() extended** to map the three new cases
3. **Help topic renderers added** with accurate usage info
Three-line change to parse_local_help_action:
"help" => LocalHelpTopic::Meta,
"submit" => LocalHelpTopic::Submit,
"resume" => LocalHelpTopic::Resume,
Dispatch order (parse_args):
1. --resume parsing
2. parse_local_help_action() ← NOW catches help/submit/resume --help
3. parse_single_word_command_alias()
4. parse_subcommand() ← Credential check happens here
## Dogfood Verification
Before fix (all three):
$ claw help --help
[error-kind: missing_credentials]
error: missing Anthropic credentials...
After fix:
$ claw help --help
Help
Usage claw help [--output-format <format>]
Purpose show the full CLI help text (all subcommands, flags, environment)
...
$ claw submit --help
Submit
Usage claw submit [--session <id|latest>] <prompt-text>
Purpose send a prompt to an existing managed session
Requires valid Anthropic credentials (when actually submitting)
...
$ claw resume --help
Resume
Usage claw resume [<session-id|latest>]
Purpose restart an interactive REPL attached to a managed session
...
## Non-Regression Verification
- `claw help` (no --help) → still shows full CLI help ✅
- `claw submit "text"` (with prompt) → still requires credentials ✅
- `claw resume` (bare) → still emits slash command guidance ✅
- All 180 binary tests pass ✅
- All 466 library tests pass ✅
## Regression Tests Added (6 assertions)
- `help --help` → routes to HelpTopic(Meta)
- `submit --help` → routes to HelpTopic(Submit)
- `resume --help` → routes to HelpTopic(Resume)
- Short forms: `help -h`, `submit -h`, `resume -h` all work
## Pattern Note
This is Category A of #130e (dispatch-order bugs). Same class as #251.
Category B (surface-parity: plugins, prompt) will be handled in a
follow-up commit/branch.
## Help-Parity Sweep Status
After cycle #52 (#130c diff, #130d config), help sweep revealed:
| Command | Before | After This Commit |
|---|---|---|
| help --help | missing_credentials | ✅ Meta help |
| submit --help | missing_credentials | ✅ Submit help |
| resume --help | missing_credentials | ✅ Resume help |
| plugins --help | "Unknown action" | ⏳ #130e-B (next) |
| prompt --help | wrong help | ⏳ #130e-B (next) |
## Related
- Closes #130e Category A (dispatch-order help fixes)
- Same bug class as #251 (session verbs)
- Stacks on #130d (config help) on same worktree branch
- #130e Category B (plugins, prompt) queued for follow-up
## What Was Broken (ROADMAP #130d, filed cycle #52)
`claw config --help` was silently ignored — the command executed and
displayed the config dump instead of showing help:
$ claw config --help
Config
Working directory /private/tmp/dogfood-probe-47
Loaded files 0
Merged keys 0
(displays full config, not help)
Expected: help for the config command. Actual: silent acceptance of
`--help`, runs config display anyway.
This is the opposite outlier from #130c (which rejected help with an
error). Together they form the help-parity anomaly:
- #130c `diff --help` → error (rejects help)
- #130d `config --help` → silent ignore (runs command, ignores help)
- Others (status, mcp, export) → proper help
- Expected behavior: all commands should show help on `--help`
## Root Cause (Traced)
At main.rs:1050, the `"config"` parser arm parsed arguments positionally:
"config" => {
let tail = &rest[1..];
let section = tail.first().cloned();
// ... ignores unrecognized args like --help silently
Ok(CliAction::Config { section, ... })
}
Unlike the `diff` arm (#130c), `config` had no explicit check for
extra args. It positionally parsed the first arg as an optional
`section` and silently accepted/ignored any trailing arg, including
`--help`.
## What This Fix Does
Same pattern as #130c (help-surface parity):
1. **LocalHelpTopic enum extended** with new `Config` variant
2. **parse_local_help_action() extended** to map `"config"` → `LocalHelpTopic::Config`
3. **config arm guard added**: check for help flag before parsing section
4. **Help topic renderer added**: human-readable help text for config
Fix locus at main.rs:1050:
"config" => {
// #130d: accept --help / -h and route to help topic
if rest.len() >= 2 && is_help_flag(&rest[1]) {
return Ok(CliAction::HelpTopic(LocalHelpTopic::Config));
}
let tail = &rest[1..];
// ... existing parsing continues
}
## Dogfood Verification
Before fix:
$ claw config --help
Config
Working directory ...
Loaded files 0
(no help, runs config)
After fix:
$ claw config --help
Config
Usage claw config [--cwd <path>] [--output-format <format>]
Purpose merge and display the resolved configuration
Options --cwd overrides the workspace directory
Output loaded files and merged key-value pairs
Formats text (default), json
Related claw status · claw doctor · claw init
Short form `claw config -h` also works.
## Non-Regression Verification
- `claw config` (no args) → still displays config dump ✅
- `claw config permissions` (section arg) → still works ✅
- All 180 binary tests pass ✅
- All 466 library tests pass ✅
## Regression Tests Added (4 assertions)
- `config --help` → routes to `HelpTopic(LocalHelpTopic::Config)`
- `config -h` (short form) → routes to help topic
- bare `config` (no args) → still routes to `Config` action
- `config permissions` (with section) → still works correctly
## Pattern Note
#130c and #130d form a pair: two outlier failure modes in help
handling for local introspection commands:
- #130c `diff` rejected help (loud error) → fixed with guard + routing
- #130d `config` silently ignored help (silent accept) → fixed with same pattern
Both are now consistent with the rest of the CLI (status, mcp, export, etc.).
## Related
- Closes #130d (config help discoverability gap)
- Completes help-parity family (#130c, #130d)
- Stacks on #130c (diff help fix) on same worktree branch
- Part of help-consistency thread (#141 audit)
## What Was Broken (ROADMAP #130c, filed cycle #50)
`claw diff --help` was rejected with:
[error-kind: unknown]
error: unexpected extra arguments after `claw diff`: --help
Other local introspection commands accept --help fine:
- `claw status --help` → shows help ✅
- `claw mcp --help` → shows help ✅
- `claw export --help` → shows help ✅
- `claw diff --help` → error ❌ (outlier)
This is a help-surface parity bug: `diff` is the only local command
that rejects --help as "extra arguments" before the help detector
gets a chance to run.
## Root Cause (Traced)
At main.rs:1063, the `"diff"` parser arm rejected ALL extra args:
"diff" => {
if rest.len() > 1 {
return Err(format!("unexpected extra arguments after `claw diff`: {}", ...));
}
Ok(CliAction::Diff { output_format })
}
When parsing `["diff", "--help"]`, `rest.len() > 1` was true (length
is 2) and `--help` was rejected as extra argument.
Other commands (status, sandbox, doctor, init, state, export, etc.)
routed through `parse_local_help_action()` which detected
`--help` / `-h` and routed to a LocalHelpTopic. The `diff` arm
lacked this guard.
## What This Fix Does
Three minimal changes:
1. **LocalHelpTopic enum extended** with new `Diff` variant
2. **parse_local_help_action() extended** to map `"diff"` → `LocalHelpTopic::Diff`
3. **diff arm guard added**: check for help flag before extra-args validation
4. **Help topic renderer added**: human-readable help text for diff command
Fix locus at main.rs:1063:
"diff" => {
// #130c: accept --help / -h as first argument and route to help topic
if rest.len() == 2 && is_help_flag(&rest[1]) {
return Ok(CliAction::HelpTopic(LocalHelpTopic::Diff));
}
if rest.len() > 1 { /* existing error */ }
Ok(CliAction::Diff { output_format })
}
## Dogfood Verification
Before fix:
$ claw diff --help
[error-kind: unknown]
error: unexpected extra arguments after `claw diff`: --help
After fix:
$ claw diff --help
Diff
Usage claw diff [--output-format <format>]
Purpose show local git staged + unstaged changes
Requires workspace must be inside a git repository
...
And `claw diff -h` (short form) also works.
## Non-Regression Verification
- `claw diff` (no args) → still routes to Diff action correctly
- `claw diff foo` (unknown arg) → still rejected as "unexpected extra arguments"
- `claw diff --output-format json` (valid flag) → still works
- All 180 binary tests pass
- All 466 library tests pass
## Regression Tests Added (4 assertions)
- `diff --help` → routes to HelpTopic(LocalHelpTopic::Diff)
- `diff -h` (short form) → routes to HelpTopic(LocalHelpTopic::Diff)
- bare `diff` → still routes to Diff action
- `diff foo` (unknown arg) → still errors with "extra arguments"
## Pattern
Follows #141 help-consistency work (extending LocalHelpTopic to
cover more subcommands). Clean surface-parity fix: identify the
outlier, add the missing guard. Low-risk, high-clarity.
## Related
- Closes #130c (diff help discoverability gap)
- Stacks on #130b (filesystem context) and #251 (session dispatch)
- Part of help-consistency thread (#141 audit, #145 plugins wiring)
## What Was Broken (ROADMAP #130b, filed cycle #47)
In a fresh workspace, running:
claw export latest --output /private/nonexistent/path/file.jsonl --output-format json
produced:
{"error":"No such file or directory (os error 2)","hint":null,"kind":"unknown","type":"error"}
This violates the typed-error contract:
- Error message is a raw errno string with zero context
- Does not mention the operation that failed (export)
- Does not mention the target path
- Classifier defaults to "unknown" even though the code path knows
this is a filesystem I/O error
## Root Cause (Traced)
run_export() at main.rs:~6915 does:
fs::write(path, &markdown)?;
When this fails:
1. io::Error propagates via ? to main()
2. Converted to string via .to_string() in error handler
3. classify_error_kind() cannot match "os error" or "No such file"
4. Defaults to "kind": "unknown"
The information is there at the source (operation name, target path,
io::ErrorKind) but lost at the propagation boundary.
## What This Fix Does
Three changes:
1. **New helper: contextualize_io_error()** (main.rs:~260)
Wraps an io::Error with operation name + target path into a
recognizable message format:
"{operation} failed: {target} ({error})"
2. **Classifier branch added** (classify_error_kind at main.rs:~270)
Recognizes the new format and classifies as "filesystem_io_error":
else if message.contains("export failed:") ||
message.contains("diff failed:") ||
message.contains("config failed:") {
"filesystem_io_error"
}
3. **run_export() wired** (main.rs:~6915)
fs::write() call now uses .map_err() to enrich io::Error:
fs::write(path, &markdown).map_err(|e| -> Box<dyn std::error::Error> {
contextualize_io_error("export", &path.display().to_string(), e).into()
})?;
## Dogfood Verification
Before fix:
{"error":"No such file or directory (os error 2)","kind":"unknown","type":"error"}
After fix:
{"error":"export failed: /private/nonexistent/path/file.jsonl (No such file or directory (os error 2))","kind":"filesystem_io_error","type":"error"}
The envelope now tells downstream claws:
- WHAT operation failed (export)
- WHERE it failed (the path)
- WHAT KIND of failure (filesystem_io_error)
- The original errno detail preserved for diagnosis
## Non-Regression Verification
- Successful export still works (emits "kind": "export" envelope as before)
- Session not found error still emits "session_not_found" (not filesystem)
- missing_credentials still works correctly
- cli_parse still works correctly
- All 180 binary tests pass
- All 466 library tests pass
- All 95 compat-harness tests pass
## Regression Tests Added
Inside the main CliAction test function:
- "export failed:" pattern classifies as "filesystem_io_error" (not "unknown")
- "diff failed:" pattern classifies as "filesystem_io_error"
- "config failed:" pattern classifies as "filesystem_io_error"
- contextualize_io_error() produces a message containing operation name
- contextualize_io_error() produces a message containing target path
- Messages produced by contextualize_io_error() are classifier-recognizable
## Scope
This is the minimum viable fix: enrich export's fs::write with context.
Future work (filed as part of #130b scope): apply same pattern to
other filesystem operations (diff, plugins, config fs reads, session
store writes, etc.). Each application is a copy-paste of the same
helper pattern.
## Pattern
Follows #145 (plugins parser interception), #248-249 (arm-level leak
templates). Helper + classifier + call site wiring. Minimal diff,
maximum observability gain.
## Related
- Closes #130b (filesystem error context preservation)
- Stacks on top of #251 (dispatch-order fix) — same worktree branch
- Ground truth for future #130 broader sweep (other io::Error sites)
## What Was Broken (ROADMAP #251)
Session-management verbs (list-sessions, load-session, delete-session,
flush-transcript) were falling through to the parser's `_other => Prompt`
catchall at main.rs:~1017. This construed them as `CliAction::Prompt {
prompt: "list-sessions", ... }` which then required credentials via the
Anthropic API path. The result: purely-local session operations emitted
`missing_credentials` errors instead of session-layer envelopes.
## Acceptance Criterion
The fix's essential requirement (stated by gaebal-gajae):
**"These 4 verbs stop falling through to Prompt and emitting `missing_credentials`."**
Not "all 4 are fully implemented to spec" — stubs are acceptable for
delete-session and flush-transcript as long as they route LOCALLY.
## What This Fix Does
Follows the exact pattern from #145 (plugins) and #146 (config/diff):
1. **CliAction enum** (main.rs:~700): Added 4 new variants.
2. **Parser** (main.rs:~945): Added 4 match arms before the `_other => Prompt`
catchall. Each arm validates the verb's positional args (e.g., load-session
requires a session-id) and rejects extra arguments.
3. **Dispatcher** (main.rs:~455):
- list-sessions → dispatches to `runtime::session_control::list_managed_sessions_for()`
- load-session → dispatches to `runtime::session_control::load_managed_session_for()`
- delete-session → emits `not_yet_implemented` error (local, not auth)
- flush-transcript → emits `not_yet_implemented` error (local, not auth)
## Dogfood Verification
Run on clean environment (no credentials):
```bash
$ env -i PATH=$PATH HOME=$HOME claw list-sessions --output-format json
{
"command": "list-sessions",
"sessions": [
{"id": "session-1775777421902-1", ...},
...
]
}
# ✓ Session-layer envelope, not auth error
$ env -i PATH=$PATH HOME=$HOME claw load-session nonexistent --output-format json
{"error":"session not found: nonexistent", "kind":"session_not_found", ...}
# ✓ Local session_not_found error, not missing_credentials
$ env -i PATH=$PATH HOME=$HOME claw delete-session test-id --output-format json
{"command":"delete-session","error":"not_yet_implemented","kind":"not_yet_implemented","type":"error"}
# ✓ Local not_yet_implemented, not auth error
$ env -i PATH=$PATH HOME=$HOME claw flush-transcript test-id --output-format json
{"command":"flush-transcript","error":"not_yet_implemented","kind":"not_yet_implemented","type":"error"}
# ✓ Local not_yet_implemented, not auth error
```
Regression sanity:
```bash
$ claw plugins --output-format json # #145 still works
$ claw prompt "hello" --output-format json # still requires credentials correctly
$ claw list-sessions extra arg --output-format json # rejects extra args with cli_parse
```
## Regression Tests Added
Inside `removed_login_and_logout_subcommands_error_helpfully` test function:
- `list-sessions` → CliAction::ListSessions (both text and JSON output)
- `load-session <id>` → CliAction::LoadSession with session_reference
- `delete-session <id>` → CliAction::DeleteSession with session_id
- `flush-transcript <id>` → CliAction::FlushTranscript with session_id
- Missing required arg errors (load-session and delete-session without ID)
- Extra args rejection (list-sessions with extra positional args)
All 180 binary tests pass. 466 library tests pass.
## Fix Scope vs. Full Implementation
This fix addresses #251 (dispatch-order bug) and #250's Option A (implement
the surfaces). list-sessions and load-session are fully functional via
existing runtime::session_control helpers. delete-session and flush-transcript
are stubbed with local "not yet implemented" errors to satisfy #251's
acceptance criterion without requiring additional session-store mutations
that can ship independently in a follow-up.
## Template
Exact same pattern as #145 (plugins) and #146 (config/diff): top-level
verb interception → CliAction variant → dispatcher with local operation.
## Related
Closes#251. Addresses #250 Option A for 4 verbs. Does not block #250
Option B (documentation scope guards) which remains valuable.
Verbs with CLI-reserved positional-arg meanings (resume, compact, memory,
commit, pr, issue, bughunter) were falling through to Prompt dispatch
when invoked with args, causing users to see 'missing_credentials' errors
instead of guidance that the verb is a slash command.
#160 investigation revealed the underlying design question: which verbs
are 'promptable' (can start a prompt like 'explain this pattern') vs.
'reserved' (have specific CLI meaning like 'resume SESSION_ID')?
This fix implements the reserved-verb classification: at parse time,
intercept reserved verbs with trailing args and emit slash-command guidance
before falling through to Prompt. Promptable verbs (explain, bughunter, clear)
continue to route to Prompt as before.
Helper: is_reserved_semantic_verb() lists the reserved set.
All 181 tests pass (no regressions).
## What Was Broken
`claw doctor` reported "Status: ok" when run from ~/ or /, but `claw
prompt` in the same directory would error out with:
error: claw is running from a very broad directory (/Users/yeongyu).
The agent can read and search everything under this path.
Diagnostic deception: doctor said green, prompt said red. User runs
doctor to check their setup, sees all green, runs prompt, gets blocked.
Trust in doctor erodes.
This is the exact pattern captured in the 'Diagnostic Commands Must Be
At Least As Strict As Runtime Commands' principle recorded in ROADMAP.md
at cycle #57.
## Root Cause
Two code paths perform the broad-cwd check:
- CliAction::Prompt handler → `enforce_broad_cwd_policy()` (errors out)
- CliAction::Repl handler → same function
But render_doctor_report() never called detect_broad_cwd(). The workspace
health check only looked at whether cwd was inside a git project, not
whether cwd was a dangerously broad path.
## What This Fix Does
Extend `check_workspace_health()` to also probe `detect_broad_cwd()`:
let broad_cwd = detect_broad_cwd();
let (level, summary) = match (in_repo, &broad_cwd) {
(_, Some(path)) => (
DiagnosticLevel::Warn,
format!(
"current directory is a broad path ({}); Prompt/REPL will \
refuse to run here without --allow-broad-cwd",
path.display()
),
),
(true, None) => (DiagnosticLevel::Ok, "project root detected"),
(false, None) => (DiagnosticLevel::Warn, "not inside a git project"),
};
The check now warns about BOTH failure modes with clear messaging about
what Prompt/REPL will do.
## Dogfood Verification
Before fix:
$ cd ~ && claw doctor
Workspace
Status warn
Summary current directory is not inside a git project
[all green otherwise]
$ echo | claw prompt "test"
error: claw is running from a very broad directory (/Users/yeongyu)...
After fix:
$ cd ~ && claw doctor
Workspace
Status warn
Summary current directory is a broad path (/Users/yeongyu);
Prompt/REPL will refuse to run here without
--allow-broad-cwd
$ cd / && claw doctor
Workspace
Status warn
Summary current directory is a broad path (/); ...
Non-regression:
$ cd /tmp/my-project && claw doctor
Workspace
Status warn
Summary current directory is not inside a git project
(unchanged)
$ cd /path/to/real/git/project && claw doctor
Workspace
Status ok
Summary project root detected on branch main
(unchanged)
## Regression Tests Added
- `workspace_check_in_project_dir_reports_ok` — non-broad + in-project = OK
- `workspace_check_outside_project_reports_warn` — non-broad + not-in-project = Warn with 'not inside git project' summary
- 181 binary tests pass (was 179, added 2)
## Related
- Principle: 'Diagnostic Commands Must Be At Least As Strict As Runtime
Commands' (ROADMAP.md cycle #57)
- Companion to #122 (stale-base preflight in doctor)
- Sibling: next step is probably a full runtime-vs-doctor audit for
other asymmetries (auth, sandbox, plugins, hooks)
Provides:
- Recommended merge order (P0 → P1 → P2 → P3 by cluster)
- Per-cluster merge prerequisites and validation steps
- Conflict risk assessment (Cluster 2 #122/#122b have same edit locus)
- Post-merge validation checklist (build + test + dogfood)
- Timeline estimate (~60 min for full 17-branch queue)
Addresses the final integration step: once branches are reviewed, knowing
the safe merge order matters. This artifact pre-answers that question.
Applied doctrine: integration-support artifacts (cycle #64) reduce reviewer
friction. At 17-branch saturation, a merge-safe checklist is first-class work.
Relates to cycle #70 integration throughput initiative.