Feature Request: Better Hook Error Disclosure

Resolved 💬 3 comments Opened Jan 22, 2026 by kitaekatt Closed Feb 28, 2026

TLDR

Hook authors frequently misconfigure JSON output, resulting in phantom error labels, missing error context, and user/agent confusion. Users spend hours debugging hooks that are "working" because the symptoms don't point to the root cause (incomplete JSON output). Claude Code should validate hook output and surface configuration errors clearly.

Related issues: #17088 #12667 #21504

Proposed UX Framework for Error Disclosure

Principle 1: Agent Error Disclosure
When an error occurs, sufficient details must be disclosed to the agent to inform corrective action.

Principle 2: User Error Disclosure
When an error occurs, sufficient details must be disclosed to the user to enable engagement with the agent.

Principle 3: Agent/User Error Disclosure Consistency
Error disclosure must occur to both user and agent. If disclosed only to the user, the user will perceive the agent as ignoring the error. If disclosed only to the agent, the user won't understand the agent's remedial actions.

Principle 4: Audience-Appropriate Formatting
User disclosure should be concise and scannable. Agent disclosure may include additional structured details for investigation.

Issue #17088 demonstrates user confusion when these principles are violated: users reported "hook error" labels appearing when hooks succeeded—a Principle 2 violation that caused significant debugging confusion.

Issue #12667 demonstrates a Principle 2 and Principle 4 violation in Stop hooks: when a Stop hook intentionally blocks with valid JSON and exit code 0, Claude Code labels the output "Stop hook error:"—misleading users into thinking the hook failed when it succeeded. The label is not audience-appropriate (P4) because "error" implies failure, and the resulting user disclosure is inaccurate (P2), causing users to debug hooks that are working as designed.

Issue #21504 proposed removing the blocking hook message when JSON output is complete and well formatted to improve the user experience.

Current Behavior

Hook JSON output controls disclosure. Incomplete output violates the principles:

Case 1: No JSON output (exit 1)

User sees: "hook error"
Agent sees: "Hook denied this tool"
Violation: P1, P2 (insufficient disclosure to both)

Case 2: Minimal JSON

{"continue": true, "hookSpecificOutput": {"hookEventName": "PreToolUse"}}
User sees: "(No content)"
Agent sees: "Hook denied this tool"
Violation: P1, P2, P3 (insufficient disclosure to agent, *NO* disclosure to user)

Case 3: Agent-only disclosure (permissionDecisionReason)

{"continue": true, "hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Skill required: tool-read"}}
User sees: "(No content) -- system-reminder messages are invisible"
Agent sees: "Skill required: tool-read" (via <system-reminder>message</system-reminder>)
Violation: P2, P3 (user lacks disclosure agent has)

Case 4: User-only disclosure

{"continue": true, "suppressOutput": false, "systemMessage": "Skill required: tool-read", "hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny"}}
User sees: "PreToolUse:Bash says: Skill required: tool-read"
Agent sees: "Hook denied this tool (no visibility into systemMessage)"
Violation: P1, P3 (agent lacks disclosure user has)

Case 5: Complete disclosure

{"continue": true, "suppressOutput": false, "systemMessage": "Skill required: tool-read", "hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Skill required: tool-read"}}
User sees: "PreToolUse:Bash says: Skill required: tool-read"
Agent sees: "Skill required: tool-read" (via injection)
Satisfies: P1, P2, P3, P4

Improved Error Handling Feature

Request: Validate hook JSON output and auto-populate missing fields where possible.

Auto-Population Behavior

When systemMessage is missing but permissionDecisionReason is provided, auto-populate systemMessage with permissionDecisionReason. This ensures both user and agent receive the same error information by default (satisfying P3).

Likewise when permissionDecisionReason is missing auto-populate systemMessage.

| Scenario | Behavior | Principles |
|----------|----------|------------|
| Only permissionDecisionReason provided | Auto-populate systemMessage | P1, P2, P3 satisfied |
| Only systemMessage provided | Auto-populate permissionDecisionReason | P1, P2, P3 satisfied |
| Both provided | Agent and user receive crafted messaging | P1, P2, P3, P4 satisfied |

Validation and Error Reporting

When hook output is incomplete and cannot be auto-populated, Claude Code should:

  1. Detect missing required fields for the hook type
  2. Report to user: "Hook [path] returned incomplete output: missing [field]. See [docs link]"
  3. Report to agent: Same message plus structured details (hook path, received output, expected schema)

Validation rules for PreToolUse blocking hooks:

| Missing | Behavior |
|---------|----------|
| No JSON | Error: "Hook returned no JSON output" |
| No hookSpecificOutput | Error: "Hook missing hookSpecificOutput structure" |
| No permissionDecisionReason | Auto-populate from systemMessage " |
| No systemMessage | Auto-populate from permissionDecisionReason |

Example of Complete Output (satisfies P1, P2, P3, P4)

{
  "continue": true,
  "suppressOutput": false,
  "systemMessage": "Skill required: tool-read. Invoke: Skill(skill: \"tool-read\")",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Skill required: tool-read. Invoke: Skill(skill: \"tool-read\")"
  }
}

Benefits

  1. Surfaces configuration errors at the source rather than letting them cascade into confusing symptoms
  2. Reduces boilerplate - hook authors only need to provide one message
  3. Backwards compatible - existing hooks with permissionDecisionReason would start working better
  4. Explicit suppression - systemMessage: "" is clearer than a missing field
  5. Default behavior satisfies P3 - same information disclosed to both user and agent

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗