ADR-0001

accepted

JSON stdout output

2026-02-19

Context

klaudiush originally communicated validation failures by writing formatted text to stderr and exiting with code 2. This had two problems:

  1. Claude Code conflates exit-code-2 hook blocks with user permission denials, causing the model to stop and ask the user for approval instead of fixing the error and retrying.
  2. Using systemMessage alone (without permissionDecisionReason) means Claude receives only a generic "Hook denied this tool" message and never sees the actual validation error.

Decision

Switch entirely to JSON stdout, always exit 0. Exit code 2 is removed. Only exit 3 (crash/panic) remains non-zero.

The JSON structure uses permissionDecision ("allow" or "deny"), permissionDecisionReason with the error code and fix hint for Claude to self-correct, and additionalContext to tell Claude this is an automated check, not a user denial.

Consequences

Claude sees the actual error and fix hint, enabling self-correction. Exception bypasses are cleaner: "allow" with context instead of block-then-convert. Single exit code eliminates the conflation with user permission denials.

Any external tooling that checked for exit code 2 must be updated to parse JSON stdout.

© 2026 Smykla Skalski Labs