Official Documentation: https://code.claude.com/docs/en/hooks
Last Updated: 2026-03-09
| Event Name | When It Fires | Equivalent Gemini CLI Event |
|---|---|---|
| PreToolUse | Before tool execution | BeforeTool |
| PostToolUse | After tool execution | AfterTool |
| PostToolUseFailure | After tool failure | (not in Gemini) |
| UserPromptSubmit | User message submission | BeforeAgent |
| Stop | Session/agent termination | AfterAgent |
| SubagentStart | Subagent spawn | (not in Gemini) |
| SubagentStop | Subagent completion | (not in Gemini) |
| SessionStart | Session initialization | SessionStart |
| SessionEnd | Session termination | SessionEnd |
| PermissionRequest | Permission prompt to user | (not in Gemini) |
| Notification | System notifications | Notification |
| TaskCompleted | Task completion event | (not in Gemini) |
| TeammateIdle | Teammate idle state | (not in Gemini) |
| PreCompact | Before context compression | PreCompress |
| Setup | Plugin setup/initialization | (not in Gemini) |
| Elicitation | Claude asks user a question | (not in Gemini) |
| ElicitationResult | User responds to elicitation | (not in Gemini) |
| ConfigChange | Configuration change detected | (not in Gemini) |
| WorktreeCreate | Git worktree created | (not in Gemini) |
| WorktreeRemove | Git worktree removed | (not in Gemini) |
| InstructionsLoaded | CLAUDE.md/instructions loaded | (not in Gemini) |
Write- Write new file or overwrite existingEdit- Edit file with find/replaceRead- Read file contentsGlob- File pattern matchingGrep- Content search
Bash- Execute bash command
Task- Launch subagent
WebFetch- Fetch web contentWebSearch- Web search
ExitPlanMode- Exit plan mode (accept/reject plan)EnterPlanMode- Enter plan mode
AskUserQuestion- Ask user for inputTaskCreate,TaskUpdate,TaskList,TaskGet- Task management
- Pattern not explicitly documented
- Likely similar to Gemini:
mcp__<server>__<tool>
{
"session_id": "string", // Session identifier
"transcript_path": "string", // Path to session transcript
"permission_mode": "auto|manual",// Permission mode
"hook_event_name": "PreToolUse" // Event type
}{
"tool_name": "Write", // Tool being executed (PascalCase)
"tool_input": { // Tool-specific input
"file_path": "/path/to/file",
"content": "file content"
}
}{
"session_id": "string",
"transcript_path": "string",
"permission_mode": "auto|manual"
}{
"decision": "approve|block", // Top-level decision (legacy)
"continue": true, // Keep session running
"stopReason": "", // Reason to stop (if continue:false)
"suppressOutput": false, // Hide hook output
"systemMessage": "string", // Message to user
"hookSpecificOutput": {
"hookEventName": "PreToolUse", // ✅ MUST match Claude Code event name
"permissionDecision": "allow|deny|ask", // Actual decision
"permissionDecisionReason": "string", // Reason shown to user
"updatedInput": {} // Modified tool input (optional)
}
}CRITICAL: The hookEventName field MUST use Claude Code event names:
- ✅ Correct:
"hookEventName": "PreToolUse" - ❌ Wrong:
"hookEventName": "BeforeTool"(Gemini event name)
{
"decision": "approve", // Must be "approve"
"continue": true,
"stopReason": "",
"suppressOutput": false,
"systemMessage": "",
"hookSpecificOutput": {
"additionalContext": "string" // Injected context
}
}| Value | Behavior |
|---|---|
approve |
Tool executes (legacy field) |
block |
Tool blocked (legacy field) |
| Value | Behavior |
|---|---|
allow |
Tool executes immediately |
deny |
Tool blocked, session continues |
ask |
Show confirmation prompt to user |
Note: Claude Code uses hookSpecificOutput.permissionDecision as the actual decision field. The top-level decision is legacy.
All fields use snake_case:
session_id(notsessionId)transcript_path(nottranscriptPath)permission_mode(notpermissionMode)hook_event_name(nothookEventName)
Exception: hookSpecificOutput and its nested fields use camelCase:
hookSpecificOutput(nothook_specific_output)permissionDecision(notpermission_decision)permissionDecisionReason(notpermission_decision_reason)additionalContext(notadditional_context)
{
"decision": "block",
"continue": true,
"stopReason": "",
"suppressOutput": false,
"systemMessage": "",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "File creation blocked by policy"
}
}{
"decision": "block",
"continue": true,
"stopReason": "",
"suppressOutput": false,
"systemMessage": "",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "ask",
"permissionDecisionReason": "Dangerous command detected. Use 'trash' instead of 'rm'?"
}
}{
"decision": "approve",
"continue": true,
"stopReason": "",
"suppressOutput": false,
"systemMessage": "Plan exported successfully"
}{
"decision": "approve",
"continue": true,
"stopReason": "",
"suppressOutput": false,
"systemMessage": "",
"hookSpecificOutput": {
"additionalContext": "Current autorun stage: Stage 2 (Critical Evaluation)"
}
}When implementing a hook daemon that serves both Claude Code and Gemini CLI:
Incoming Normalization (Request):
- Claude sends:
"hook_event_name": "PreToolUse"→ use as-is internally - Gemini sends:
"type": "BeforeTool"→ normalize to"PreToolUse"
Outgoing Denormalization (Response):
- For Claude: Internal
"PreToolUse"→ keep as"PreToolUse" - For Gemini: Internal
"PreToolUse"→ convert to"BeforeTool"
See plugins/clautorun/src/clautorun/core.py for reference implementation:
GEMINI_EVENT_MAP- Request normalization (line 88-95)get_cli_event_name()- Response denormalization (line 119-131)
Problem: Claude Code ignores permissionDecision: "deny" when hook exits with code 0.
Workaround: Use permissionDecision: "ask" instead of "deny" to show user prompt with reason.
Status: Unfixed as of Claude Code v1.0.62+
References:
- GitHub Issue: #4669
- Documentation: https://code.claude.com/docs/en/hooks#known-issues
Problem: When hook exits with code 2, stderr message goes to Claude instead of user.
Workaround: Use JSON response with permissionDecision: "ask" instead of exit code 2.
Status: Unfixed
- ❌ Using Gemini CLI event names in responses (
"BeforeTool"instead of"PreToolUse") - ❌ Using top-level
decisionfield instead ofhookSpecificOutput.permissionDecision - ❌ Hardcoding event names without CLI detection
- ❌ Using snake_case for hookSpecificOutput fields (
permission_decisioninstead ofpermissionDecision) - ❌ Missing tool names in hook matchers (e.g., forgetting
ExitPlanMode) - ❌ Relying on
decision: "block"withouthookSpecificOutput.permissionDecision: "deny"
- Gemini CLI Hooks API:
notes/gemini-cli-hooks-api.md - Daemon implementation:
plugins/clautorun/src/clautorun/core.py - Hook configuration:
plugins/clautorun/hooks/claude-hooks.json - Official Claude docs: https://code.claude.com/docs/en/hooks