claw-code/tests
YeonGyu-Kim 39769a3863 feat: #178 — argparse errors emit JSON envelope when --output-format json requested
Dogfood pinpoint: running 'claw nonexistent-command --output-format json' bypasses
the JSON envelope contract — argparse dumps human-readable usage to stderr with
exit 2, breaking the SCHEMAS.md guarantee that JSON mode returns structured output.

Problem:
  $ claw nonexistent --output-format json
  usage: main.py [-h] {summary,manifest,...} ...
  main.py: error: argument command: invalid choice: 'nonexistent' (choose from ...)
  [exit 2 — no envelope, claws must parse argparse usage messages]

Fix:
  $ claw nonexistent --output-format json
  {
    "timestamp": "2026-04-22T11:00:29Z",
    "command": "nonexistent-command",
    "exit_code": 1,
    "output_format": "json",
    "schema_version": "1.0",
    "error": {
      "kind": "parse",
      "operation": "argparse",
      "target": "nonexistent-command",
      "retryable": false,
      "message": "invalid command or argument (argparse rejection)",
      "hint": "run with no arguments to see available subcommands"
    }
  }
  [exit 1, clean JSON envelope on stdout per SCHEMAS.md]

Changes:
- src/main.py:
  - _wants_json_output(argv): pre-scan for --output-format json before parsing
  - _emit_parse_error_envelope(argv, message): emit wrapped envelope on stdout
  - main(): catch SystemExit from argparse; if JSON requested, emit envelope
    instead of letting argparse's help dump go through

- tests/test_parse_error_envelope.py (new, 9 tests):
  - TestParseErrorJsonEnvelope (7): unknown command, =syntax, text mode unchanged,
    invalid flag, missing command, valid command unaffected, common fields
  - TestParseErrorSchemaCompliance (2): error.kind='parse', retryable=false

Contract:
- text mode (default): unchanged — argparse dumps help to stderr, exits 2
- JSON mode: envelope per SCHEMAS.md, error.kind='parse', exit 1
- Parse errors always retryable=false (typo won't self-fix)
- error.kind='parse' already enumerated in SCHEMAS.md (no schema changes)

This closes a real gap: claws invoking unknown commands in JSON mode can now route
via exit code + envelope.kind='parse' instead of scraping argparse output.

Test results: 192 → 201 passing, 3 skipped unchanged, zero regression.

Pinpoint discovered via dogfood at 2026-04-22 19:59 KST (cycle #19).
2026-04-30 11:03:06 +09:00
..
test_cancel_observed_field.py feat: #164 Stage B CLOSURE — turn-loop JSON + cancel_observed coverage + CLAWABLE promotion 2026-04-30 11:03:06 +09:00
test_cli_parity_audit.py feat: #164 Stage B CLOSURE — turn-loop JSON + cancel_observed coverage + CLAWABLE promotion 2026-04-30 11:03:06 +09:00
test_command_graph_tool_pool_output_format.py fix: #169 — command-graph and tool-pool now accept --output-format; diagnostic inventory JSON parity 2026-04-30 11:03:06 +09:00
test_exec_route_bootstrap_output_format.py fix: #168 — exec-command / exec-tool / route / bootstrap now accept --output-format; CLI family JSON parity COMPLETE 2026-04-30 11:03:06 +09:00
test_flush_transcript_cli.py fix: #166 — flush-transcript now accepts --directory / --output-format / --session-id; session-creation command parity with #160/#165 lifecycle triplet 2026-04-30 11:03:06 +09:00
test_json_envelope_field_consistency.py feat: #173 — wrap_json_envelope() applied to all 13 clawable commands (LOOP CLOSED) 2026-04-30 11:03:06 +09:00
test_load_session_cli.py feat: #173 — wrap_json_envelope() applied to all 13 clawable commands (LOOP CLOSED) 2026-04-30 11:03:06 +09:00
test_parse_error_envelope.py feat: #178 — argparse errors emit JSON envelope when --output-format json requested 2026-04-30 11:03:06 +09:00
test_porting_workspace.py feat: #173 — wrap_json_envelope() applied to all 13 clawable commands (LOOP CLOSED) 2026-04-30 11:03:06 +09:00
test_run_turn_loop_cancellation.py fix: #164 Stage A — cooperative cancellation via cancel_event in submit_message 2026-04-30 11:03:06 +09:00
test_run_turn_loop_continuation.py fix: #163 — remove [turn N] suffix pollution from run_turn_loop; file #164 timeout-cancellation followup 2026-04-30 11:03:06 +09:00
test_run_turn_loop_permissions.py fix: #159 — run_turn_loop no longer hardcodes empty denied_tools; permission denials now parity-match bootstrap_session 2026-04-30 11:03:06 +09:00
test_run_turn_loop_timeout.py fix: #164 Stage A — cooperative cancellation via cancel_event in submit_message 2026-04-30 11:03:06 +09:00
test_session_store.py fix(#160): harden delete_session contract — idempotency, race-safety, typed partial-failure 2026-04-30 11:03:06 +09:00
test_show_command_tool_output_format.py fix: #167 — show-command and show-tool now accept --output-format flag; CLI parity with session-lifecycle family 2026-04-30 11:03:06 +09:00
test_submit_message_budget.py fix: #162 — budget-overflow no longer corrupts session state in submit_message 2026-04-30 11:03:06 +09:00
test_submit_message_cancellation.py fix: #164 Stage A — cooperative cancellation via cancel_event in submit_message 2026-04-30 11:03:06 +09:00