Official Documentation: https://geminicli.com/docs/hooks/reference/
Last Updated: 2026-02-15
| Event Name | When It Fires | Equivalent Claude Code Event |
|---|---|---|
| BeforeTool | Before tool execution | PreToolUse |
| AfterTool | After tool execution | PostToolUse |
| BeforeAgent | Before agent spawn/user prompt | UserPromptSubmit |
| AfterAgent | After agent completion | Stop |
| BeforeModel | Before model API call | (not in Claude Code) |
| AfterModel | After model response | (not in Claude Code) |
| BeforeToolSelection | Before tool selection phase | (not in Claude Code) |
| SessionStart | Session initialization | SessionStart |
| SessionEnd | Session termination | SessionEnd |
| Notification | System notifications | Notification |
| PreCompress | Before context compression | PreCompact |
read_file- Read file contentswrite_file- Write new file or overwrite existingedit_file- Edit file with find/replacereplace- Replace text in file
glob- File pattern matchinggrep- Content search
execute_bash- Execute bash commandrun_shell_command- Run shell command
web_search- Web searchweb_fetch- Fetch web content
exit_plan_mode- Exit plan mode (accept/reject plan)enter_plan_mode- Enter plan mode
- Pattern:
mcp__<server>__<tool> - Example:
mcp__github__create_issue
{
"session_id": "string", // Session identifier
"transcript_path": "string", // Path to session transcript
"cwd": "string", // Current working directory
"hook_event_name": "BeforeTool", // Event type (NOT "type")
"type": "BeforeTool" // Legacy field, use hook_event_name
}{
"tool_name": "write_file", // Tool being executed (snake_case)
"tool_input": { // Tool-specific input
"file_path": "/path/to/file",
"content": "file content"
}
}{
"session_id": "string",
"transcript_path": "string",
"cwd": "string"
}{
"decision": "allow|deny|ask", // Top-level decision
"reason": "string", // Reason shown to user
"updated_input": {}, // Modified tool input (optional)
"continue": true, // Keep session running
"stopReason": "", // Reason to stop (if continue:false)
"suppressOutput": false, // Hide hook output
"systemMessage": "string" // Message to user
}For BeforeTool events, include hookSpecificOutput:
{
"hookSpecificOutput": {
"hookEventName": "BeforeTool", // ✅ MUST match Gemini event name
"permissionDecision": "allow|deny|ask", // Same as top-level decision
"permissionDecisionReason": "string" // Same as reason
}
}CRITICAL: The hookEventName field MUST use Gemini event names:
- ✅ Correct:
"hookEventName": "BeforeTool" - ❌ Wrong:
"hookEventName": "PreToolUse"(causes "Invalid hook event name" warning)
| Value | Behavior |
|---|---|
allow |
Tool executes immediately |
deny |
Tool blocked, session continues |
ask |
Show confirmation prompt to user |
All fields use snake_case:
session_id(notsessionId)transcript_path(nottranscriptPath)tool_name(nottoolName)tool_input(nottoolInput)hook_event_name(nothookEventName)
Exception: hookSpecificOutput is camelCase (inherited from Claude Code API)
{
"decision": "deny",
"reason": "File creation blocked by policy",
"continue": true,
"stopReason": "",
"suppressOutput": false,
"systemMessage": "File creation blocked by policy",
"hookSpecificOutput": {
"hookEventName": "BeforeTool",
"permissionDecision": "deny",
"permissionDecisionReason": "File creation blocked by policy"
}
}{
"decision": "allow",
"continue": true,
"stopReason": "",
"suppressOutput": false,
"systemMessage": "Plan exported successfully"
}When implementing a hook daemon that serves both Gemini CLI and Claude Code:
Incoming Normalization (Request):
- Gemini sends:
"type": "BeforeTool"→ normalize to internal"PreToolUse" - Claude sends:
"hook_event_name": "PreToolUse"→ use as-is
Outgoing Denormalization (Response):
- For Gemini: Internal
"PreToolUse"→ convert to"BeforeTool" - For Claude: Internal
"PreToolUse"→ keep as"PreToolUse"
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)
- ❌ Using Claude Code event names in responses (
"PreToolUse"instead of"BeforeTool") - ❌ Using camelCase field names (
sessionIdinstead ofsession_id) - ❌ Hardcoding event names without CLI detection
- ❌ Forgetting to include both
decisionandhookSpecificOutput.permissionDecision - ❌ Missing tool names in hook matchers (e.g., forgetting
exit_plan_mode)
- Claude Code Hooks API:
notes/claude-code-hooks-api.md - Daemon implementation:
plugins/clautorun/src/clautorun/core.py - Hook configuration:
plugins/clautorun/hooks/hooks.json - Official Gemini docs: https://geminicli.com/docs/hooks/reference/