Files
Claude-Code-Game-Studios/.claude/docs/hooks-reference/hook-input-schemas.md
T
DonchitosandClaude Opus 5.5 b21fa0f7f2 Release 1.1.2 — the default path works end to end
Fix release for 1.1.1: the minimal path end to end, engine commands that
run on Godot, Unity and Unreal, hook warnings you can see, and verdicts
that say when they could not check. Details in CHANGELOG.md; upgrade
steps in UPGRADING.md (v1.1.1 → v1.1.2).

Fixes #67
Fixes #131

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WuJn2FdynEybfL5KC697nJ
2026-09-29 21:32:19 +10:00

6.3 KiB

Hook Input/Output Schemas

This documents the JSON payloads each Claude Code hook receives on stdin for every event type, and how a hook's output reaches the user and Claude. Every payload also carries the common fields session_id, transcript_path, cwd and hook_event_name; the examples below show the event-specific ones. The full reference is the Claude Code hooks documentation (https://code.claude.com/docs/en/hooks).

PreToolUse

Fired before a tool is executed. Exit 2 blocks the call. Exit 0 lets it continue through the normal permission flow — it does not approve it, and the user can still be asked. To warn without blocking, exit 0 and print hook JSON — see Output below.

PreToolUse: Bash

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "git commit -m 'feat: add player health system'",
    "description": "Commit changes with message",
    "timeout": 120000
  }
}

The PowerShell tool sends the same shape with "tool_name": "PowerShell", so a matcher of Bash|PowerShell covers both.

PreToolUse: Write

{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "src/gameplay/health.gd",
    "content": "extends Node\n..."
  }
}

PreToolUse: Edit

{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "src/gameplay/health.gd",
    "old_string": "var health = 100",
    "new_string": "var health: int = 100"
  }
}

PreToolUse: Read

{
  "tool_name": "Read",
  "tool_input": {
    "file_path": "src/gameplay/health.gd"
  }
}

In real payloads a file tool's file_path is absolute, with the platform's own separators — backslashes on Windows. Strip the project root first, then match from the start of what is left (assets/data/). A segment such as /assets/data/ matched anywhere also fires on a parent folder of the same name and on files outside the project, such as the user's own ~/.claude/skills/. validate-assets.sh shows the stripping.

PostToolUse

Fired after a tool completes. Cannot block — the tool already ran. Exit 2 shows stderr to Claude, so it can fix what it just wrote. stderr on exit 0 goes to the debug log only, so an advisory warning must be hook JSON on stdout.

PostToolUse: Write

{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "assets/data/enemy_stats.json",
    "content": "{\"goblin\": {\"health\": 50}}"
  },
  "tool_response": {
    "filePath": "assets/data/enemy_stats.json",
    "type": "create"
  }
}

PostToolUse: Edit

{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "assets/data/enemy_stats.json",
    "old_string": "\"health\": 50",
    "new_string": "\"health\": 75"
  },
  "tool_response": { "filePath": "assets/data/enemy_stats.json" }
}

SubagentStart

Fired when a subagent is spawned via the Agent tool (named Task before Claude Code 2.1.63; Task still works as an alias in permission rules and tools: frontmatter, but the hook payload's tool_name is now Agent). The agent's name is in agent_type — there is no agent_name field.

{
  "agent_id": "agent-abc123",
  "agent_type": "game-designer"
}

SessionStart

Fired when a session starts, resumes, is cleared with /clear, or is compacted. Stdin carries source: startup, resume, clear, compact or fork. Plain stdout is added to Claude's context — this is the event that restores context after a compaction.

PreCompact

Fired before context compaction. Stdin carries trigger (manual or auto). Its output does not reach Claude: stdout goes to the debug log and systemMessage is discarded. Exit 2 blocks the compaction.

Stop

Fired every time Claude finishes a response, not once when the session ends. Stdin carries the common fields plus stop_hook_active and last_assistant_message. Exit 2 keeps Claude working instead of stopping.

Output: warnings someone sees

A hook that warns but must not block exits 0 — and on exit 0, stderr reaches no one. Print a JSON object on stdout instead:

{
  "systemMessage": "Shown to the user",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Given to Claude next to the tool result"
  }
}
  • stdout must hold only that object.
  • hookEventName is required inside hookSpecificOutput.
  • Leave out permissionDecision unless you mean it: on PreToolUse, "allow" skips the user's permission prompt.

CCGS hooks use hook_warn from .claude/hooks/yaml-helper.sh, which builds this object and escapes the message without needing jq:

. .claude/hooks/yaml-helper.sh
hook_warn PostToolUse "Asset names must be lowercase: $FILE_PATH"
exit 0

Exit Code Reference

Exit Code Meaning
0 Success. A JSON object on stdout is read as hook output; other stdout goes to the debug log, except on UserPromptSubmit, UserPromptExpansion, SessionStart and PostModelSwitch, where it is added to Claude's context. stderr goes to the debug log only.
2 Blocks on PreToolUse (Claude sees stderr as the reason), UserPromptSubmit (the prompt is erased), UserPromptExpansion, Stop, SubagentStop (the subagent keeps working) and PreCompact. On PostToolUse it shows stderr to Claude — the tool already ran. On SessionStart, SubagentStart, PostCompact and PostModelSwitch it shows stderr to the user only. On Notification it is ignored. The full per-event table is in the Claude Code hooks documentation.
Other Non-blocking error; the action proceeds.

Output is capped at 10,000 characters (observed; the Claude Code hooks documentation does not state it). Plain stdout, and each of additionalContext, systemMessage and initialUserMessage, is measured on its own. Past the cap Claude Code saves the text to a file and passes a path plus a 2,000-character preview, and nothing tells Claude to read the file. Keep what Claude must see inside the cap — session-start.sh caps its checkpoint preview for this reason.

Notes

  • Hooks receive JSON on stdin (pipe). Use INPUT=$(cat) to capture.
  • Parse with jq if available, fall back to grep for cross-platform compatibility.
  • On Windows, grep -P (Perl regex) is often unavailable. Use grep -E (POSIX extended) instead.
  • Path separators may be \ on Windows. Normalize with sed 's|\\|/|g' when comparing paths.