docs: ERROR_HANDLING.md — fix code examples to match v1.0 envelope (flat shape)

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.
This commit is contained in:
YeonGyu-Kim
2026-04-23 05:00:33 +09:00
parent 98c675b33b
commit 0929180ba8

View File

@@ -15,7 +15,7 @@ Every clawable command returns JSON on stdout when `--output-format json` is req
| Exit Code | Meaning | Response Format | Example | | Exit Code | Meaning | Response Format | Example |
|---|---|---|---| |---|---|---|---|
| **0** | Success | `{success fields}` | `{"session_id": "...", "loaded": true}` | | **0** | Success | `{success fields}` | `{"session_id": "...", "loaded": true}` |
| **1** | Error / Not Found | `{error: {kind, message, ...}}` | `{"error": {"kind": "session_not_found", ...}}` | | **1** | Error / Not Found | `{error: "...", hint: "...", kind: "...", type: "error"}` (flat, v1.0) | `{"error": "session not found", "kind": "session_not_found", "type": "error"}` |
| **2** | Timeout | `{final_stop_reason: "timeout", final_cancel_observed: ...}` | `{"final_stop_reason": "timeout", ...}` | | **2** | Timeout | `{final_stop_reason: "timeout", final_cancel_observed: ...}` | `{"final_stop_reason": "timeout", ...}` |
### Text mode vs JSON mode exit codes ### Text mode vs JSON mode exit codes
@@ -81,8 +81,12 @@ def run_claw_command(command: list[str], timeout_seconds: float = 30.0) -> dict[
retryable=False, retryable=False,
) )
# Classify by exit code and error.kind # Classify by exit code and top-level kind field (v1.0 flat envelope shape)
match (result.returncode, envelope.get('error', {}).get('kind')): # NOTE: v1.0 envelopes have error as a STRING, not a nested object.
# The v2.0 schema (SCHEMAS.md) specifies nested error.{kind, message, ...},
# but the current binary emits flat {error: "...", kind: "...", type: "error"}.
# See FIX_LOCUS_164.md for the migration timeline.
match (result.returncode, envelope.get('kind')):
case (0, _): case (0, _):
# Success # Success
return envelope return envelope
@@ -91,8 +95,8 @@ def run_claw_command(command: list[str], timeout_seconds: float = 30.0) -> dict[
# #179: argparse error — typically a typo or missing required argument # #179: argparse error — typically a typo or missing required argument
raise ClawError( raise ClawError(
kind='parse', kind='parse',
message=envelope['error']['message'], message=envelope.get('error', ''), # error field is a string in v1.0
hint=envelope['error'].get('hint'), hint=envelope.get('hint'),
retryable=False, # Typos don't fix themselves retryable=False, # Typos don't fix themselves
) )
@@ -100,7 +104,7 @@ def run_claw_command(command: list[str], timeout_seconds: float = 30.0) -> dict[
# Common: load-session on nonexistent ID # Common: load-session on nonexistent ID
raise ClawError( raise ClawError(
kind='session_not_found', kind='session_not_found',
message=envelope['error']['message'], message=envelope.get('error', ''), # error field is a string in v1.0
session_id=envelope.get('session_id'), session_id=envelope.get('session_id'),
retryable=False, # Session won't appear on retry retryable=False, # Session won't appear on retry
) )
@@ -109,7 +113,7 @@ def run_claw_command(command: list[str], timeout_seconds: float = 30.0) -> dict[
# Directory missing, permission denied, disk full # Directory missing, permission denied, disk full
raise ClawError( raise ClawError(
kind='filesystem', kind='filesystem',
message=envelope['error']['message'], message=envelope.get('error', ''), # error field is a string in v1.0
retryable=True, # Might be transient (disk space, NFS flake) retryable=True, # Might be transient (disk space, NFS flake)
) )
@@ -117,16 +121,16 @@ def run_claw_command(command: list[str], timeout_seconds: float = 30.0) -> dict[
# Generic engine error (unexpected exception, malformed input, etc.) # Generic engine error (unexpected exception, malformed input, etc.)
raise ClawError( raise ClawError(
kind='runtime', kind='runtime',
message=envelope['error']['message'], message=envelope.get('error', ''), # error field is a string in v1.0
retryable=envelope['error'].get('retryable', False), retryable=envelope.get('retryable', False), # v1.0 may or may not have this
) )
case (1, _): case (1, _):
# Catch-all for any new error.kind values # Catch-all for any new error.kind values
raise ClawError( raise ClawError(
kind=envelope['error']['kind'], kind=envelope.get('kind', 'unknown'),
message=envelope['error']['message'], message=envelope.get('error', ''), # error field is a string in v1.0
retryable=envelope['error'].get('retryable', False), retryable=envelope.get('retryable', False), # v1.0 may or may not have this
) )
case (2, _): case (2, _):
@@ -456,9 +460,28 @@ def test_error_handler_not_found():
--- ---
## Appendix: SCHEMAS.md Error Shape ## Appendix A: v1.0 Error Envelope (Current Binary)
For reference, the canonical JSON error envelope shape (SCHEMAS.md): The actual shape emitted by the current binary (v1.0, flat):
```json
{
"error": "session 'nonexistent' not found in .claw/sessions",
"hint": "use 'list-sessions' to see available sessions",
"kind": "session_not_found",
"type": "error"
}
```
**Key differences from v2.0 schema (below):**
- `error` field is a **string**, not a structured object
- `kind` is at **top-level**, not nested under `error`
- Missing: `timestamp`, `command`, `exit_code`, `output_format`, `schema_version`
- Extra: `type: "error"` field (not in schema)
## Appendix B: SCHEMAS.md Target Shape (v2.0)
For reference, the target JSON error envelope shape (SCHEMAS.md, v2.0):
```json ```json
{ {
@@ -466,7 +489,7 @@ For reference, the canonical JSON error envelope shape (SCHEMAS.md):
"command": "load-session", "command": "load-session",
"exit_code": 1, "exit_code": 1,
"output_format": "json", "output_format": "json",
"schema_version": "1.0", "schema_version": "2.0",
"error": { "error": {
"kind": "session_not_found", "kind": "session_not_found",
"operation": "session_store.load_session", "operation": "session_store.load_session",
@@ -478,7 +501,7 @@ For reference, the canonical JSON error envelope shape (SCHEMAS.md):
} }
``` ```
All commands that emit errors follow this shape (with error.kind varying). See `SCHEMAS.md` for the complete contract. **This is the target schema after [`FIX_LOCUS_164`](./FIX_LOCUS_164.md) is implemented.** The migration plan includes a dual-mode `--envelope-version=2.0` flag in Phase 1, default version bump in Phase 2, and deprecation in Phase 3. For now, code against v1.0 (Appendix A).
--- ---