Independent reference, not affiliated with Google. Tables are generated from the Gemini CLI source at v0.62.0 (v0.62.0). Official documentation: geminicli.com.
Gemini CLI runs hooks on 11 lifecycle events, from BeforeTool to BeforeToolSelection. A hook is a shell command that receives a JSON payload on stdin and can answer with JSON on stdout to block, rewrite or annotate what happens next. Hooks live in the hooks block of settings.json or in an extension's hooks/hooks.json.
NONE wins, else ANY, else AUTO; function names unioned
llm_request
Three events can rewrite model traffic rather than tool calls: BeforeModel edits the request or answers it with a synthetic response, AfterModel replaces each streamed response chunk, and BeforeToolSelection narrows which functions the model may call. When several hooks match one event they run in parallel and their outputs are merged as shown in the last column: for tool and agent events a single block from any hook blocks.
AfterAgent works like Claude Code's Stop hook: "decision": "block" sends the reason back to the model as a new prompt, and the retried turn arrives with stop_hook_active: true so the hook can avoid looping forever.
Empty or * matches everything. Tool events: passed to new RegExp() and tested against the tool name, unanchored, so write the pattern without /slashes/; an invalid regex falls back to exact match. Trigger events: exact string match.
sequential
definition
boolean
true runs every matching hook for the event one after another, each seeing the previous hook's changes; default is parallel
type
hook
"command"
Only command hooks can be configured in settings.json.
command
hook
string
Shell command to execute. Receives JSON input via stdin and returns JSON output via stdout.
name
hook
string
Unique identifier for the hook.
description
hook
string
A description of the hook.
timeout
hook
number
Milliseconds. Default 60000 (60 s); on expiry SIGTERM, then SIGKILL 5 s later.
env
hook
Record<string, string>
Extra environment variables for this command.
The same hooks object can go in ~/.gemini/settings.json and in a project's .gemini/settings.json. The per-event arrays are concatenated across files, so user and project hooks both run. An extension ships the identical shape in hooks/hooks.json under a top-level "hooks" key.
A separate hooksConfig block switches the system on and off:
Key
Type
Default
Notes
hooksConfig.enabled
boolean
true
Canonical toggle for the hooks system. When disabled, no hooks will be executed.
hooksConfig.disabled
string[]
[]
List of hook names (commands) that should be disabled. Hooks in this list will not execute even if configured.
hooksConfig.notifications
boolean
true
Show visual indicators when hooks are executing.
Hooks from settings.json run only in trusted folders; in an untrusted folder they are skipped. The first time a trusted project's .gemini/settings.json contains hooks you have not seen, Gemini CLI prints a warning listing them, records them in ~/.gemini/trusted_hooks.json, and runs them. It does not ask for approval first.
JSON on stdout decides first. {"decision": "deny", "reason": "..."} (or "block") from a BeforeTool hook stops the call, and the model receives Tool execution blocked: <reason>. "decision": "ask" replaces the policy engine's verdict with a confirmation prompt (a policy deny included), hookSpecificOutput.tool_input replaces the arguments, and "continue": false stops the whole agent loop with stopReason.
When the output is not JSON, Gemini CLI converts the text (stdout, or stderr when stdout is empty) by exit code:
Exit code
Non-JSON output (stdout, else stderr) becomes
Counted as
0
decision allow, text shown as systemMessage
success
1
decision allow, systemMessage "Warning: <text>"
failure (warning, not blocking)
2 or higher
decision deny, text as reason
failure
Two consequences for plain-text hooks: exit 1 never blocks, it only shows a warning, and any exit of 2 or above blocks only when the hook printed some text to use as the reason. JSON output is honored whatever the exit code. A hook that exits 2 silently is logged as failed and the tool call proceeds. Any non-zero exit, timeout or spawn error shows "Hook(s) [name] failed for event ..." in the UI.
Every field below works on every event; hookSpecificOutput carries the event-specific fields listed further down.
The command runs through the platform shell with the session's working directory as its cwd, the sanitized parent environment, the hook's own env, and these variables:
Variable
Value
Also substituted in command text
GEMINI_PROJECT_DIR
session working directory
yes
GEMINI_PLANS_DIR
plans directory
yes
GEMINI_CWD
session working directory
yes
GEMINI_SESSION_ID
session id
yes
CLAUDE_PROJECT_DIR
session working directory
yes
Occurrences of $GEMINI_PROJECT_DIR, $GEMINI_CWD, $GEMINI_PLANS_DIR, $GEMINI_SESSION_ID and $CLAUDE_PROJECT_DIR in the command string are replaced with shell-escaped values before the shell sees them.
How do Gemini CLI hooks compare to Claude Code hooks?#
The model is the same (JSON on stdin, JSON on stdout, matcher plus hooks array), but the names differ and so does the timeout unit. gemini hooks migrate --from-claude reads .claude/settings.local.json (or .claude/settings.json when there is no local file), renames events and tool names in matchers, swaps $CLAUDE_PROJECT_DIR for $GEMINI_PROJECT_DIR, and writes the result to the project's .gemini/settings.json.
gemini hooks migrate --from-claude copies timeout unchanged. Claude Code timeouts are seconds and Gemini CLI reads milliseconds, so multiply migrated values by 1000.
Claude Code event
Gemini CLI event
PreToolUse
BeforeTool
PostToolUse
AfterTool
UserPromptSubmit
BeforeAgent
Stop
AfterAgent
SubAgentStop
AfterAgent
SessionStart
SessionStart
SessionEnd
SessionEnd
PreCompact
PreCompress
Notification
Notification
Claude Code tool
Gemini CLI tool
Edit
replace
Bash
run_shell_command
Read
read_file
Write
write_file
Glob
glob
Grep
grep
LS
ls
Other differences: Gemini CLI has no PermissionRequest or SubagentStart event, exit code 1 is a warning rather than an error, and BeforeModel, AfterModel and BeforeToolSelection have no Claude Code equivalent. CLAUDE_PROJECT_DIR is still set in the hook environment, so scripts that read it keep working.
Yes. Gemini CLI has 11 hook events, including BeforeTool, AfterTool, BeforeAgent, AfterAgent, SessionStart, SessionEnd, PreCompress and Notification, plus three model-level events. Hooks are shell commands configured in the hooks block of settings.json or in an extension's hooks/hooks.json, and the system is on by default through hooksConfig.enabled.
The usual causes: the folder is not trusted, so settings hooks are skipped; hooksConfig.enabled is false or the hook's name is in hooksConfig.disabled; the matcher is written as /pattern/, which the regex treats as literal slashes; the timeout was set in seconds (30 means 30 ms); or stdout mixes log lines with JSON, so the whole output is read as plain text.
Is the Gemini CLI hook timeout in seconds or milliseconds?#
Milliseconds. The default is 60000 (60 seconds). When it expires Gemini CLI sends SIGTERM, then SIGKILL five seconds later, and reports the hook as failed. Hooks converted with gemini hooks migrate --from-claude keep Claude Code's value unchanged, so a Claude timeout of 30 seconds becomes 30 milliseconds unless you multiply it by 1000.
How do I send a notification when Gemini CLI needs input?#
Add a Notification hook. It fires with notification_type set to ToolPermission when a tool call is waiting for your confirmation, along with a message and a details object describing the call. The matcher is ignored for this event and the hook cannot change the outcome, so it suits desktop alerts, sounds or chat pings.
Yes, from BeforeTool. Print {"decision": "deny", "reason": "..."} and the call is skipped with Tool execution blocked: <reason>. Plain text with exit code 2 or higher also blocks, using the text as the reason; plain text with exit code 1 only produces a warning. "decision": "ask" forces a confirmation instead of blocking outright.
How do Gemini CLI hooks compare to Claude Code hooks?#
The configuration shape and stdin/stdout JSON contract match, but the names differ: PreToolUse is BeforeTool, PostToolUse is AfterTool, UserPromptSubmit is BeforeAgent, Stop is AfterAgent, PreCompact is PreCompress. Matchers use Gemini tool names such as run_shell_command and replace, and timeouts are milliseconds. gemini hooks migrate --from-claude converts an existing Claude Code configuration.
Yes, in a trusted folder. New hooks in a project's .gemini/settings.json trigger a one-time warning that lists them, are then recorded in ~/.gemini/trusted_hooks.json, and run without an approval prompt. In an untrusted folder no hooks from settings.json run at all, so trusting a folder also means trusting its hooks.