Problem
When a PreToolUse hook intentionally blocks a command (exit code 2), the output is labeled:
PreToolUse:Bash hook error: [message]
This is misleading — the hook didn't error, it intentionally denied the operation. There's no way to distinguish between "the hook crashed" and "the hook blocked this command by design."
Use case
We use PreToolUse hooks as a CLI governance layer — enforcing read-only AWS access, blocking destructive git operations, preventing production writes, etc. These are intentional policy denials, not errors. The "hook error" label confuses users into thinking something is broken.
Proposed solution
Differentiate the label based on exit code or hook output:
- Exit 2 (blocking denial):
PreToolUse:Bash hook denied: [stderr message]
- Non-zero other (actual error):
PreToolUse:Bash hook error: [stderr message]
Alternatively, allow hooks to set a custom label via structured JSON output (e.g., {"label": "BLOCKED", "message": "..."}).
Current workaround
We prefix our stderr messages with BLOCKED: to make intent clear, but the outer "hook error" label still appears and causes confusion.
Problem
When a PreToolUse hook intentionally blocks a command (exit code 2), the output is labeled:
This is misleading — the hook didn't error, it intentionally denied the operation. There's no way to distinguish between "the hook crashed" and "the hook blocked this command by design."
Use case
We use PreToolUse hooks as a CLI governance layer — enforcing read-only AWS access, blocking destructive git operations, preventing production writes, etc. These are intentional policy denials, not errors. The "hook error" label confuses users into thinking something is broken.
Proposed solution
Differentiate the label based on exit code or hook output:
PreToolUse:Bash hook denied: [stderr message]PreToolUse:Bash hook error: [stderr message]Alternatively, allow hooks to set a custom label via structured JSON output (e.g.,
{"label": "BLOCKED", "message": "..."}).Current workaround
We prefix our stderr messages with
BLOCKED:to make intent clear, but the outer "hook error" label still appears and causes confusion.