Phase 0 Task 3 of the JSON Productization Program: per-verb emission inventory.
Documents the actual binary behavior as of v1.5 (post-#168c fix, pre-Phase 1
shape normalization). Reference artifact for consumers building against v1.5,
not a target schema.
Catalog contents:
- 12 verbs using 'kind' field (help, version, doctor, mcp, skills, agents,
sandbox, status, system-prompt, bootstrap-plan, export, acp)
- 1 verb using 'command' field (list-sessions) — Phase 1 normalization target
- 3 error-only verbs in test env (bootstrap, dump-manifests, state)
- Standard error envelope: {error, hint, kind, type} flat shape
- 9 machine-readable error kinds from classify_error_kind
Emission contract locked by:
- Task 1 (#168c routing fix, cycle #89)
- Task 2 (no-silent guarantee test, cycle #90)
- This catalog (human-readable reference, cycle #91)
Consumer guidance + Phase 1 normalization targets documented.
Phase 0 progress:
- Task 1 Stream routing fix
- Task 2 No-silent guarantee test
- Task 3 Per-verb emission inventory
- Task 4 pending: CI parity test
Refs: #168c, cycle #91, Phase 0 Task 3
24 KiB
JSON Envelope Schemas — Clawable CLI Contract
⚠️ CRITICAL: This document describes the TARGET v2.0 envelope schema, not the current v1.0 binary behavior. The Rust binary currently emits a flat v1.0 envelope that does NOT include
timestamp,command,exit_code,output_format, orschema_versionfields. SeeFIX_LOCUS_164.mdfor the full migration plan and timeline. Do not build automation against the field shapes below without first testing against the actual binary output. Useclaw <command> --output-format jsonto inspect what your binary version actually emits.
This document locks the target field-level contract for all clawable-surface commands. After the v1.0→v2.0 migration (FIX_LOCUS_164 Phase 2), every command accepting --output-format json will conform to the envelope shapes documented here.
Target audience: Claws planning v2.0 migration, reference implementers, contract validators.
Current v1.0 reality: See ERROR_HANDLING.md Appendix A for the flat envelope shape the binary actually emits today.
Common Fields (All Envelopes) — TARGET v2.0 SCHEMA
This section describes the v2.0 target schema. The current v1.0 binary does NOT emit these fields. See FIX_LOCUS_164.md for the migration timeline.
After v2.0 migration, every command response, success or error, will carry:
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "list-sessions",
"exit_code": 0,
"output_format": "json",
"schema_version": "2.0"
}
| Field | Type | Required | Notes |
|---|---|---|---|
timestamp |
ISO 8601 UTC | Yes | Time command completed |
command |
string | Yes | argv[1] (e.g. "list-sessions") |
exit_code |
int (0/1/2) | Yes | 0=success, 1=error/not-found, 2=timeout |
output_format |
string | Yes | Always "json" (for symmetry with text mode) |
schema_version |
string | Yes | "1.0" (bump for breaking changes) |
Turn Result Fields (Multi-Turn Sessions)
When a command's response includes a turn object (e.g., in bootstrap or turn-loop), it carries:
| Field | Type | Required | Notes |
|---|---|---|---|
prompt |
string | Yes | User input for this turn |
output |
string | Yes | Assistant response |
stop_reason |
enum | Yes | One of: completed, timeout, cancelled, max_budget_reached, max_turns_reached |
cancel_observed |
bool | Yes | #164 Stage B: cancellation was signaled and observed (#161/#164) |
Error Envelope
When a command fails (exit code 1), responses carry:
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "exec-command",
"exit_code": 1,
"error": {
"kind": "filesystem",
"operation": "write",
"target": "/tmp/nonexistent/out.md",
"retryable": true,
"message": "No such file or directory",
"hint": "intermediate directory does not exist; try mkdir -p /tmp/nonexistent"
}
}
| Field | Type | Required | Notes |
|---|---|---|---|
error.kind |
enum | Yes | One of: filesystem, auth, session, parse, runtime, mcp, delivery, usage, policy, unknown |
error.operation |
string | Yes | Syscall/method that failed (e.g. "write", "open", "resolve_session") |
error.target |
string | Yes | Resource that failed (path, session-id, server-name, etc.) |
error.retryable |
bool | Yes | Whether caller can safely retry without intervention |
error.message |
string | Yes | Platform error message (e.g. errno text) |
error.hint |
string | No | Optional actionable next step |
Not-Found Envelope
When an entity does not exist (exit code 1, but not a failure):
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "load-session",
"exit_code": 1,
"name": "does-not-exist",
"found": false,
"error": {
"kind": "session_not_found",
"message": "session 'does-not-exist' not found in .claw/sessions/",
"retryable": false
}
}
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | Yes | Entity name/id that was looked up |
found |
bool | Yes | Always false for not-found |
error.kind |
enum | Yes | One of: command_not_found, tool_not_found, session_not_found |
error.message |
string | Yes | User-visible explanation |
error.retryable |
bool | Yes | Usually false (entity will not magically appear) |
Per-Command Success Schemas
list-sessions
Status: ✅ Implemented (closed #251 cycle #45, 2026-04-23).
Actual binary envelope (as of #251 fix):
{
"command": "list-sessions",
"sessions": [
{
"id": "session-1775777421902-1",
"path": "/path/to/.claw/sessions/session-1775777421902-1.jsonl",
"updated_at_ms": 1775777421902,
"message_count": 0
}
]
}
Aspirational (future) shape:
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "list-sessions",
"exit_code": 0,
"output_format": "json",
"schema_version": "1.0",
"directory": ".claw/sessions",
"sessions_count": 2,
"sessions": [
{
"session_id": "sess_abc123",
"created_at": "2026-04-21T15:30:00Z",
"last_modified": "2026-04-22T09:45:00Z",
"prompt_count": 5,
"stopped": false
}
]
}
Gap: Current impl lacks timestamp, exit_code, output_format, schema_version, directory, sessions_count (derivable), and the session object uses id/updated_at_ms/message_count instead of session_id/last_modified/prompt_count. Follow-up #250 Option B to align field names and add common-envelope fields.
delete-session
Status: ⚠️ Stub only (closed #251 dispatch-order fix; full impl deferred).
Actual binary envelope (as of #251 fix):
{
"type": "error",
"command": "delete-session",
"error": "not_yet_implemented",
"kind": "not_yet_implemented"
}
Exit code: 1. No credentials required. The stub ensures the verb does NOT fall through to Prompt/auth (the #251 fix), but the actual delete operation is not yet wired.
Aspirational (future) shape:
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "delete-session",
"exit_code": 0,
"session_id": "sess_abc123",
"deleted": true,
"directory": ".claw/sessions"
}
load-session
Status: ✅ Implemented (closed #251 cycle #45, 2026-04-23).
Actual binary envelope (as of #251 fix):
{
"command": "load-session",
"session": {
"id": "session-abc123",
"path": "/path/to/.claw/sessions/session-abc123.jsonl",
"messages": 5
}
}
For nonexistent sessions, emits a local session_not_found error (NOT missing_credentials):
{
"error": "session not found: nonexistent",
"kind": "session_not_found",
"type": "error",
"hint": "Hint: managed sessions live in .claw/sessions/<hash>/ ..."
}
Aspirational (future) shape:
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "load-session",
"exit_code": 0,
"session_id": "sess_abc123",
"loaded": true,
"directory": ".claw/sessions",
"path": ".claw/sessions/sess_abc123.jsonl"
}
Gap: Current impl uses nested session: {...} instead of flat fields, and omits common-envelope fields. Follow-up #250 Option B to align.
flush-transcript
Status: ⚠️ Stub only (closed #251 dispatch-order fix; full impl deferred).
Actual binary envelope (as of #251 fix):
{
"type": "error",
"command": "flush-transcript",
"error": "not_yet_implemented",
"kind": "not_yet_implemented"
}
Exit code: 1. No credentials required. Like delete-session, this stub resolves the #251 dispatch-order bug but the actual flush operation is not yet wired.
Aspirational (future) shape:
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "flush-transcript",
"exit_code": 0,
"session_id": "sess_abc123",
"path": ".claw/sessions/sess_abc123.jsonl",
"flushed": true,
"messages_count": 12,
"input_tokens": 4500,
"output_tokens": 1200
}
show-command
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "show-command",
"exit_code": 0,
"name": "add-dir",
"found": true,
"source_hint": "commands/add-dir/add-dir.tsx",
"responsibility": "creates a new directory in the worktree"
}
show-tool
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "show-tool",
"exit_code": 0,
"name": "BashTool",
"found": true,
"source_hint": "tools/BashTool/BashTool.tsx"
}
exec-command
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "exec-command",
"exit_code": 0,
"name": "add-dir",
"prompt": "create src/util/",
"handled": true,
"message": "created directory",
"source_hint": "commands/add-dir/add-dir.tsx"
}
exec-tool
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "exec-tool",
"exit_code": 0,
"name": "BashTool",
"payload": "cargo build",
"handled": true,
"message": "exit code 0",
"source_hint": "tools/BashTool/BashTool.tsx"
}
route
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "route",
"exit_code": 0,
"prompt": "add a test",
"limit": 10,
"match_count": 3,
"matches": [
{
"kind": "command",
"name": "add-file",
"score": 0.92,
"source_hint": "commands/add-file/add-file.tsx"
}
]
}
bootstrap
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "bootstrap",
"exit_code": 0,
"prompt": "hello",
"setup": {
"python_version": "3.13.12",
"implementation": "CPython",
"platform_name": "darwin",
"test_command": "pytest"
},
"routed_matches": [
{"kind": "command", "name": "init", "score": 0.85, "source_hint": "..."}
],
"turn": {
"prompt": "hello",
"output": "...",
"stop_reason": "completed"
},
"persisted_session_path": ".claw/sessions/sess_abc.jsonl"
}
command-graph
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "command-graph",
"exit_code": 0,
"builtins_count": 185,
"plugin_like_count": 20,
"skill_like_count": 2,
"total_count": 207,
"builtins": [
{"name": "add-dir", "source_hint": "commands/add-dir/add-dir.tsx"}
],
"plugin_like": [],
"skill_like": []
}
tool-pool
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "tool-pool",
"exit_code": 0,
"simple_mode": false,
"include_mcp": true,
"tool_count": 184,
"tools": [
{"name": "BashTool", "source_hint": "tools/BashTool/BashTool.tsx"}
]
}
bootstrap-graph
{
"timestamp": "2026-04-22T10:10:00Z",
"command": "bootstrap-graph",
"exit_code": 0,
"stages": ["stage 1", "stage 2", "..."],
"note": "bootstrap-graph is markdown-only in this version"
}
Versioning & Compatibility
- schema_version = "1.0": Current as of 2026-04-22. Covers all 13 clawable commands.
- Breaking changes (e.g. renaming a field) bump schema_version to "2.0".
- Additive changes (e.g. new optional field) stay at "1.0" and are backward compatible.
- Downstream claws must check
schema_versionbefore relying on field presence.
Regression Testing
Each command is covered by:
- Fixture file (golden JSON snapshot under
tests/fixtures/json/<command>.json) - Parametrised test in
test_cli_parity_audit.py::TestJsonOutputContractEndToEnd - Field consistency test (new, tracked as ROADMAP #172)
To update a fixture after a intentional schema change:
claw <command> --output-format json <args> > tests/fixtures/json/<command>.json
# Review the diff, commit
git add tests/fixtures/json/<command>.json
To verify no regressions:
cargo test --release test_json_envelope_field_consistency
Design Notes
Why common fields on every response?
- Downstream claws can build one error handler that works for all commands
- Timestamp + command + exit_code give context without scraping argv or timestamps from command output
schema_versionsignals compatibility for future upgrades
Why both "found" and "error" on not-found?
- Exit code 1 covers both "entity missing" and "operation failed"
found=falsedistinguishes not-found from error without string matchingerror.kindanderror.retryablelet automation decide: retry a temporary miss vs escalate a permanent refusal
Why "operation" and "target" in error?
- Claws can aggregate failures by operation type (e.g. "how many
writeops failed?") - Claws can implement per-target retry policy (e.g. "skip missing files, retry networking")
- Pure text errors ("No such file") do not provide enough structure for pattern matching
Why "handled" vs "found"?
show-commandreportsfound: bool(inventory signal: "does this exist?")exec-commandreportshandled: bool(operational signal: "was this work performed?")- The names matter: a command can be found but not handled (e.g. too large for context window), or handled silently (no output message)
Appendix: Current v1.0 vs. Target v2.0 Envelope Shapes
⚠️ IMPORTANT: Binary Reality vs. This Document
This entire SCHEMAS.md document describes the TARGET v2.0 schema. The actual Rust binary currently emits v1.0 (flat) envelopes.
Do not assume the fields documented above are in the binary right now. They are not.
Current v1.0 Envelope (What the Rust Binary Actually Emits)
The Rust binary in rust/ currently emits a flat v1.0 envelope without common metadata wrapper:
v1.0 Success Envelope Example
{
"kind": "list-sessions",
"sessions": [
{"id": "abc123", "created": "2026-04-22T10:00:00Z", "turns": 5}
],
"type": "success"
}
Key differences from v2.0 above:
- NO
timestamp,command,exit_code,output_format,schema_versionfields kindfield contains the verb name (or is entirely absent for success)type: "success"flag at top level- Verb-specific fields (
sessions,turn, etc.) at top level
v1.0 Error Envelope Example
{
"error": "session 'xyz789' not found in .claw/sessions",
"hint": "use 'list-sessions' to see available sessions",
"kind": "session_not_found",
"type": "error"
}
Key differences from v2.0 error above:
errorfield is a STRING, not a nested object- NO
error.operation,error.target,error.retryablestructured fields kindis at top-level, not nested- NO
timestamp,command,exit_code,output_format,schema_version - Extra
type: "error"flag
Migration Timeline (FIX_LOCUS_164)
See FIX_LOCUS_164.md for the full phased migration:
- Phase 1 (Opt-in):
claw <cmd> --output-format json --envelope-version=2.0emits v2.0 shape - Phase 2 (Default): v2.0 becomes default;
--legacy-envelopeflag opts into v1.0 - Phase 3 (Deprecation): v1.0 warnings, then removal
Building Automation Against v1.0 (Current)
For claws building automation today (against the real binary, not this schema):
- Check
typefield first (string: "success" or "error") - For success: verb-specific fields are at top level. Use
jq .kindfor verb ID (if present) - For error: access
error(string),hint(string),kind(string) all at top level - Do not expect:
timestamp,command,exit_code,output_format,schema_version— they don't exist yet - Test your code against
claw <cmd> --output-format jsonoutput to verify assumptions before deploying
Example: Python Consumer Code (v1.0)
Correct pattern for v1.0 (current binary):
import json
import subprocess
result = subprocess.run(
["claw", "list-sessions", "--output-format", "json"],
capture_output=True,
text=True
)
envelope = json.loads(result.stdout)
# v1.0: type is at top level
if envelope.get("type") == "error":
error_msg = envelope.get("error", "unknown error") # error is a STRING
error_kind = envelope.get("kind") # kind is at TOP LEVEL
print(f"Error: {error_kind} — {error_msg}")
else:
# Success path: verb-specific fields at top level
sessions = envelope.get("sessions", [])
for session in sessions:
print(f"Session: {session['id']}")
After v2.0 migration, this code will break. Claws building for v2.0 compatibility should:
- Check
schema_versionfield - Parse differently based on version
- Or wait until Phase 2 default bump is announced, then migrate
Why This Mismatch Exists
SCHEMAS.md was written as the target design for v2.0. The Rust binary is still on v1.0. The migration (FIX_LOCUS_164) will bring the binary in line with this schema, but it hasn't happened yet.
This mismatch is the root cause of doc-truthfulness issues #78, #79, #165. All three docs were documenting the v2.0 target as if it were current reality.
Questions?
- "Is v2.0 implemented?" No. The binary is v1.0. See FIX_LOCUS_164.md for the implementation roadmap.
- "Should I build against v2.0 schema?" No. Build against v1.0 (current). Test your code with
clawto verify. - "When does v2.0 ship?" See FIX_LOCUS_164.md Phase 1 estimate: ~6 dev-days. Not scheduled yet.
- "Can I use v2.0 now?" Only if you explicitly pass
--envelope-version=2.0(which doesn't exist yet in v1.0 binary).
v1.5 Emission Baseline — Per-Verb Shape Catalog (Cycle #91, Phase 0 Task 3)
Status: 📸 Snapshot of actual binary behavior as of cycle #91 (2026-04-23). Anchored by controlled matrix /tmp/cycle87-audit/matrix.json + Phase 0 tests in output_format_contract.rs.
Purpose
This section documents what each verb actually emits under --output-format json as of the v1.5 emission baseline (post-cycle #89 emission routing fix, pre-Phase 1 shape normalization).
This is a reference artifact, not a target schema. It describes the reality that:
--output-format jsonexists and emits JSON (enforced by Phase 0 Task 2)- All output goes to stdout (enforced by #168c fix, cycle #89)
- Each verb has a bespoke top-level shape (documented below; to be normalized in Phase 1)
Emission Contract (v1.5 Baseline)
| Property | Rule | Enforced By |
|---|---|---|
| Exit 0 + stdout empty (silent success) | Forbidden | Test: emission_contract_no_silent_success_under_output_format_json_168c_task2 |
| Exit 0 + stdout contains valid JSON | Required | Test: same (parses each safe-success verb) |
| Exit != 0 + JSON envelope on stdout | Required | Test: same + error_envelope_emitted_to_stdout_under_output_format_json_168c |
Error envelope on stderr under --output-format json |
Forbidden | Test: #168c regression test |
| Text mode routes errors to stderr | Preserved | Backward compat; not changed by cycle #89 |
Per-Verb Shape Catalog
Captured from controlled matrix (cycle #87) and verified against post-#168c binary (cycle #91).
Verbs with kind top-level field (12/13)
| Verb | Top-level keys | Notes |
|---|---|---|
help |
kind, message |
Minimal shape |
version |
git_sha, kind, message, target, version |
Build metadata |
doctor |
checks, has_failures, kind, message, report, summary |
Diagnostic results |
mcp |
action, config_load_error, configured_servers, kind, servers, status, working_directory |
MCP state |
skills |
action, kind, skills, summary |
Skills inventory |
agents |
action, agents, count, kind, summary, working_directory |
Agent inventory |
sandbox |
active, active_namespace, active_network, allowed_mounts, enabled, fallback_reason, filesystem_active, filesystem_mode, in_container, kind, markers, requested_namespace, requested_network, supported |
Sandbox state (14 keys) |
status |
config_load_error, kind, model, model_raw, model_source, permission_mode, sandbox, status, usage, workspace |
Runtime status |
system-prompt |
kind, message, sections |
Prompt sections |
bootstrap-plan |
kind, phases |
Bootstrap phases |
export |
file, kind, message, messages, session_id |
Export metadata |
acp |
aliases, discoverability_tracking, kind, launch_command, message, recommended_workflows, serve_alias_only, status, supported, tracking |
ACP discoverability |
Verb with command top-level field (1/13) — Phase 1 normalization target
| Verb | Top-level keys | Notes |
|---|---|---|
list-sessions |
command, sessions |
Deviation: uses command instead of kind. Target Phase 1 fix. |
Verbs with error-only emission in test env (exit != 0)
These verbs require external state (credentials, session fixtures, manifests) and return error envelopes in clean test environments:
| Verb | Error envelope keys | Notes |
|---|---|---|
bootstrap |
error, hint, kind, type |
Requires ANTHROPIC_AUTH_TOKEN for success path |
dump-manifests |
error, hint, kind, type |
Requires upstream manifest source |
state |
error, hint, kind, type |
Requires worker state file |
Common error envelope shape (all verbs): {error, hint, kind, type} — this is the one consistently-shaped part of v1.5.
Standard Error Envelope (v1.5)
Error envelopes are the only part of v1.5 with a guaranteed consistent shape across all verbs:
{
"type": "error",
"error": "short human-readable reason",
"kind": "snake_case_machine_readable_classification",
"hint": "optional remediation hint (may be null)"
}
Classification kinds (from classify_error_kind in main.rs):
cli_parse— argument parsing errormissing_credentials— auth token/key missingsession_not_found— load-session target missingsession_load_failed— persisted session unreadableno_managed_sessions— no sessions exist to listmissing_manifests— upstream manifest sources absentfilesystem_io_error— file operation failureapi_http_error— upstream API returned non-2xxunknown— classifier fallthrough
How This Differs from v2.0 Target
| Aspect | v1.5 (this doc) | v2.0 Target (SCHEMAS.md top) |
|---|---|---|
| Top-level verb ID | 12 use kind, 1 uses command |
Common command field |
| Common metadata | None (no timestamp, exit_code, etc.) |
timestamp, command, exit_code, output_format, schema_version |
| Error envelope | {error, hint, kind, type} flat |
{error: {message, kind, operation, target, retryable}, ...} nested |
| Success shape | Verb-specific (13 bespoke) | Common wrapper with data field |
Consumer Guidance (Against v1.5 Baseline)
For claws consuming v1.5 today:
- Always use
--output-format json— text format has no stability contract (#167) - Check
typefield first — "error" or absent/other (treat as success) - For errors: access
error(string),kind(string),hint(nullable string) - For success: use verb-specific keys per catalog above
- Do NOT assume
kindfield exists on success path —list-sessionsusescommandinstead - Do NOT assume metadata fields (
timestamp,exit_code, etc.) — they are v2.0 target only - Check exit code for pass/fail; don't infer from payload alone
Phase 1 Normalization Targets (After This Baseline Locks)
Phase 1 (shape stabilization) will normalize these divergences:
list-sessions:command→kind(align with 12/13 convention)- Potentially: unify where
messagefield appears (9/13 have it, inconsistently populated) - Potentially: unify where
actionfield appears (only in 4 inventory verbs)
Phase 1 does not add common metadata (timestamp, exit_code) — that's Phase 2 (v2.0 wrapper).
Regenerating This Catalog
The catalog is derived from running the controlled matrix. Phase 0 Task 4 will add a deterministic script; for now, reproduce with:
for verb in help version list-sessions doctor mcp skills agents sandbox status system-prompt bootstrap-plan export acp; do
echo "=== $verb ==="
claw $verb --output-format json | jq 'keys'
done
This matches what the Phase 0 Task 2 test enforces programmatically.