Gemini CLI hooks
Hooks are scripts or programs that Gemini CLI executes at specific points in the agentic loop, allowing you to intercept and customize behavior without modifying the CLI’s source code.
See writing hooks guide for a tutorial on creating your first hook and a comprehensive example.
See hooks reference for the technical specification of the I/O schemas.
See best practices for guidelines on security, performance, and debugging.
What are hooks?
Section titled “What are hooks?”With hooks, you can:
- Add context: Inject relevant information before the model processes a request
- Validate actions: Review and block potentially dangerous operations
- Enforce policies: Implement security and compliance requirements
- Log interactions: Track tool usage and model responses
- Optimize behavior: Dynamically adjust tool selection or model parameters
Hooks run synchronously as part of the agent loop—when a hook event fires, Gemini CLI waits for all matching hooks to complete before continuing.
Core concepts
Section titled “Core concepts”Hook events
Section titled “Hook events”Hooks are triggered by specific events in Gemini CLI’s lifecycle. The following table lists all available hook events:
| Event | When It Fires | Common Use Cases |
|---|---|---|
SessionStart | When a session begins | Initialize resources, load context |
SessionEnd | When a session ends | Clean up, save state |
BeforeAgent | After user submits prompt, before planning | Add context, validate prompts |
AfterAgent | When agent loop ends | Review output, force continuation |
BeforeModel | Before sending request to LLM | Modify prompts, add instructions |
AfterModel | After receiving LLM response | Filter responses, log interactions |
BeforeToolSelection | Before LLM selects tools (after BeforeModel) | Filter available tools, optimize selection |
BeforeTool | Before a tool executes | Validate arguments, block dangerous ops |
AfterTool | After a tool executes | Process results, run tests |
PreCompress | Before context compression | Save state, notify user |
Notification | When a notification occurs (e.g., permission) | Auto-approve, log decisions |
Hook types
Section titled “Hook types”Gemini CLI currently supports command hooks that run shell commands or scripts:
{ "type": "command", "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/my-hook.sh", "timeout": 30000}Note: Plugin hooks (npm packages) are planned for a future release.
Matchers
Section titled “Matchers”For tool-related events (BeforeTool, AfterTool), you can filter which tools
trigger the hook:
{ "hooks": { "BeforeTool": [ { "matcher": "write_file|replace", "hooks": [ /* hooks for write operations */ ] } ] }}Matcher patterns:
- Exact match:
"read_file"matches onlyread_file - Regex:
"write_.*|replace"matcheswrite_file,replace - Wildcard:
"*"or""matches all tools
Session event matchers:
- SessionStart:
startup,resume,clear - SessionEnd:
exit,clear,logout,prompt_input_exit - PreCompress:
manual,auto - Notification:
ToolPermission
Hook input/output contract
Section titled “Hook input/output contract”Command hook communication
Section titled “Command hook communication”Hooks communicate via:
- Input: JSON on stdin
- Output: Exit code + stdout/stderr
Exit codes
Section titled “Exit codes”- 0: Success - stdout shown to user (or injected as context for some events)
- 2: Blocking error - stderr shown to agent/user, operation may be blocked
- Other: Non-blocking warning - logged but execution continues
Common input fields
Section titled “Common input fields”Every hook receives these base fields:
{ "session_id": "abc123", "transcript_path": "/path/to/transcript.jsonl", "cwd": "/path/to/project", "hook_event_name": "BeforeTool", "timestamp": "2025-12-01T10:30:00Z" // ... event-specific fields}Event-specific fields
Section titled “Event-specific fields”BeforeTool
Section titled “BeforeTool”Input:
{ "tool_name": "write_file", "tool_input": { "file_path": "/path/to/file.ts", "content": "..." }}Output (JSON on stdout):
{ "decision": "allow|deny|ask|block", "reason": "Explanation shown to agent", "systemMessage": "Message shown to user"}Or simple exit codes:
- Exit 0 = allow (stdout shown to user)
- Exit 2 = deny (stderr shown to agent)
AfterTool
Section titled “AfterTool”Input:
{ "tool_name": "read_file", "tool_input": { "file_path": "..." }, "tool_response": "file contents..."}Output:
{ "decision": "allow|deny", "hookSpecificOutput": { "hookEventName": "AfterTool", "additionalContext": "Extra context for agent" }}BeforeAgent
Section titled “BeforeAgent”Input:
{ "prompt": "Fix the authentication bug"}Output:
{ "decision": "allow|deny", "hookSpecificOutput": { "hookEventName": "BeforeAgent", "additionalContext": "Recent project decisions: ..." }}BeforeModel
Section titled “BeforeModel”Input:
{ "llm_request": { "model": "gemini-2.0-flash-exp", "messages": [{ "role": "user", "content": "Hello" }], "config": { "temperature": 0.7 }, "toolConfig": { "functionCallingConfig": { "mode": "AUTO", "allowedFunctionNames": ["read_file", "write_file"] } } }}Output:
{ "decision": "allow", "hookSpecificOutput": { "hookEventName": "BeforeModel", "llm_request": { "messages": [ { "role": "system", "content": "Additional instructions..." }, { "role": "user", "content": "Hello" } ] } }}AfterModel
Section titled “AfterModel”Input:
{ "llm_request": { "model": "gemini-2.0-flash-exp", "messages": [ /* ... */ ], "config": { /* ... */ }, "toolConfig": { /* ... */ } }, "llm_response": { "text": "string", "candidates": [ { "content": { "role": "model", "parts": ["array of content parts"] }, "finishReason": "STOP" } ] }}Output:
{ "hookSpecificOutput": { "hookEventName": "AfterModel", "llm_response": { "candidate": { /* modified response */ } } }}BeforeToolSelection
Section titled “BeforeToolSelection”Input:
{ "llm_request": { "model": "gemini-2.0-flash-exp", "messages": [ /* ... */ ], "toolConfig": { "functionCallingConfig": { "mode": "AUTO", "allowedFunctionNames": [ /* 100+ tools */ ] } } }}Output:
{ "hookSpecificOutput": { "hookEventName": "BeforeToolSelection", "toolConfig": { "functionCallingConfig": { "mode": "ANY", "allowedFunctionNames": ["read_file", "write_file", "replace"] } } }}Or simple output (comma-separated tool names sets mode to ANY):
echo "read_file,write_file,replace"SessionStart
Section titled “SessionStart”Input:
{ "source": "startup|resume|clear"}Output:
{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "Loaded 5 project memories" }}SessionEnd
Section titled “SessionEnd”Input:
{ "reason": "exit|clear|logout|prompt_input_exit|other"}No structured output expected (but stdout/stderr logged).
PreCompress
Section titled “PreCompress”Input:
{ "trigger": "manual|auto"}Output:
{ "systemMessage": "Compression starting..."}Notification
Section titled “Notification”Input:
{ "notification_type": "ToolPermission", "message": "string", "details": { /* notification details */ }}Output:
{ "systemMessage": "Notification logged"}Configuration
Section titled “Configuration”Hook definitions are configured in settings.json files using the hooks
object. Configuration can be specified at multiple levels with defined
precedence rules.
Configuration layers
Section titled “Configuration layers”Hook configurations are applied in the following order of execution (lower numbers run first):
- Project settings:
.gemini/settings.jsonin your project directory (highest priority) - User settings:
~/.gemini/settings.json - System settings:
/etc/gemini-cli/settings.json - Extensions: Internal hooks defined by installed extensions (lowest priority)
Deduplication and shadowing
Section titled “Deduplication and shadowing”If multiple hooks with the identical name and command are discovered across different configuration layers, Gemini CLI deduplicates them. The hook from the higher-priority layer (e.g., Project) will be kept, and others will be ignored.
Within each level, hooks run in the order they are declared in the configuration.
Configuration schema
Section titled “Configuration schema”{ "hooks": { "EventName": [ { "matcher": "pattern", "hooks": [ { "name": "hook-identifier", "type": "command", "command": "./path/to/script.sh", "description": "What this hook does", "timeout": 30000 } ] } ] }}Configuration properties:
name(string, recommended): Unique identifier for the hook used in/hooks enable/disablecommands. If omitted, thecommandpath is used as the identifier.type(string, required): Hook type - currently only"command"is supportedcommand(string, required): Path to the script or command to executedescription(string, optional): Human-readable description shown in/hooks paneltimeout(number, optional): Timeout in milliseconds (default: 60000)matcher(string, optional): Pattern to filter when hook runs (event matchers only)
Environment variables
Section titled “Environment variables”Hooks have access to:
GEMINI_PROJECT_DIR: Project root directoryGEMINI_SESSION_ID: Current session IDGEMINI_API_KEY: Gemini API key (if configured)- All other environment variables from the parent process
Managing hooks
Section titled “Managing hooks”View registered hooks
Section titled “View registered hooks”Use the /hooks panel command to view all registered hooks:
/hooks panelThis command displays:
- All active hooks organized by event
- Hook source (user, project, system)
- Hook type (command or plugin)
- Execution status and recent output
Enable and disable hooks
Section titled “Enable and disable hooks”You can temporarily enable or disable individual hooks using commands:
/hooks enable hook-name/hooks disable hook-nameThese commands allow you to control hook execution without editing configuration
files. The hook name should match the name field in your hook configuration.
Changes made via these commands are persisted to your global User settings
(~/.gemini/settings.json).
Disabled hooks configuration
Section titled “Disabled hooks configuration”To permanently disable hooks, add them to the hooks.disabled array in your
settings.json:
{ "hooks": { "disabled": ["secret-scanner", "auto-test"] }}Note: The hooks.disabled array uses a UNION merge strategy. Disabled hooks
from all configuration levels (user, project, system) are combined and
deduplicated, meaning a hook disabled at any level remains disabled.
Migration from Claude Code
Section titled “Migration from Claude Code”If you have hooks configured for Claude Code, you can migrate them:
gemini hooks migrate --from-claudeThis command:
- Reads
.claude/settings.json - Converts event names (
PreToolUse→BeforeTool, etc.) - Translates tool names (
Bash→run_shell_command,replace→replace) - Updates matcher patterns
- Writes to
.gemini/settings.json
Event name mapping
Section titled “Event name mapping”| Claude Code | Gemini CLI |
|---|---|
PreToolUse | BeforeTool |
PostToolUse | AfterTool |
UserPromptSubmit | BeforeAgent |
Stop | AfterAgent |
Notification | Notification |
SessionStart | SessionStart |
SessionEnd | SessionEnd |
PreCompact | PreCompress |
Tool name mapping
Section titled “Tool name mapping”| Claude Code | Gemini CLI |
|---|---|
Bash | run_shell_command |
Edit | replace |
Read | read_file |
Write | write_file |
Glob | glob |
Grep | search_file_content |
LS | list_directory |
Tool and Event Matchers Reference
Section titled “Tool and Event Matchers Reference”Available tool names for matchers
Section titled “Available tool names for matchers”The following built-in tools can be used in BeforeTool and AfterTool hook
matchers:
File operations
Section titled “File operations”read_file- Read a single fileread_many_files- Read multiple files at oncewrite_file- Create or overwrite a filereplace- Edit file content with find/replace
File system
Section titled “File system”list_directory- List directory contentsglob- Find files matching a patternsearch_file_content- Search within file contents
Execution
Section titled “Execution”run_shell_command- Execute shell commands
Web and external
Section titled “Web and external”google_web_search- Google Search with groundingweb_fetch- Fetch web page content
Agent features
Section titled “Agent features”write_todos- Manage TODO itemssave_memory- Save information to memorydelegate_to_agent- Delegate tasks to sub-agents
Example matchers
Section titled “Example matchers”{ "matcher": "write_file|replace" // File editing tools}{ "matcher": "read_.*" // All read operations}{ "matcher": "run_shell_command" // Only shell commands}{ "matcher": "*" // All tools}Event-specific matchers
Section titled “Event-specific matchers”SessionStart event matchers
Section titled “SessionStart event matchers”startup- Fresh session startresume- Resuming a previous sessionclear- Session cleared
SessionEnd event matchers
Section titled “SessionEnd event matchers”exit- Normal exitclear- Session clearedlogout- User logged outprompt_input_exit- Exit from prompt inputother- Other reasons
PreCompress event matchers
Section titled “PreCompress event matchers”manual- Manually triggered compressionauto- Automatically triggered compression
Notification event matchers
Section titled “Notification event matchers”ToolPermission- Tool permission notifications
Learn more
Section titled “Learn more”- Writing Hooks - Tutorial and comprehensive example
- Best Practices - Security, performance, and debugging
- Custom Commands - Create reusable prompt shortcuts
- Configuration - Gemini CLI configuration options