ADR-0001
acceptedJSON 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:
- 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.
- Using
systemMessagealone (withoutpermissionDecisionReason) 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.