Skip to content
Gemini 3 Flash is now available. Speed no longer has to mean compromising quality. Read the announcement

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.

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.

Hooks are triggered by specific events in Gemini CLI’s lifecycle. The following table lists all available hook events:

EventWhen It FiresCommon Use Cases
SessionStartWhen a session beginsInitialize resources, load context
SessionEndWhen a session endsClean up, save state
BeforeAgentAfter user submits prompt, before planningAdd context, validate prompts
AfterAgentWhen agent loop endsReview output, force continuation
BeforeModelBefore sending request to LLMModify prompts, add instructions
AfterModelAfter receiving LLM responseFilter responses, log interactions
BeforeToolSelectionBefore LLM selects tools (after BeforeModel)Filter available tools, optimize selection
BeforeToolBefore a tool executesValidate arguments, block dangerous ops
AfterToolAfter a tool executesProcess results, run tests
PreCompressBefore context compressionSave state, notify user
NotificationWhen a notification occurs (e.g., permission)Auto-approve, log decisions

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.

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 only read_file
  • Regex: "write_.*|replace" matches write_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

Hooks communicate via:

  • Input: JSON on stdin
  • Output: Exit code + stdout/stderr
  • 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

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
}

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)

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"
}
}

Input:

{
"prompt": "Fix the authentication bug"
}

Output:

{
"decision": "allow|deny",
"hookSpecificOutput": {
"hookEventName": "BeforeAgent",
"additionalContext": "Recent project decisions: ..."
}
}

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" }
]
}
}
}

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 */
}
}
}
}

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):

Terminal window
echo "read_file,write_file,replace"

Input:

{
"source": "startup|resume|clear"
}

Output:

{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Loaded 5 project memories"
}
}

Input:

{
"reason": "exit|clear|logout|prompt_input_exit|other"
}

No structured output expected (but stdout/stderr logged).

Input:

{
"trigger": "manual|auto"
}

Output:

{
"systemMessage": "Compression starting..."
}

Input:

{
"notification_type": "ToolPermission",
"message": "string",
"details": {
/* notification details */
}
}

Output:

{
"systemMessage": "Notification logged"
}

Hook definitions are configured in settings.json files using the hooks object. Configuration can be specified at multiple levels with defined precedence rules.

Hook configurations are applied in the following order of execution (lower numbers run first):

  1. Project settings: .gemini/settings.json in your project directory (highest priority)
  2. User settings: ~/.gemini/settings.json
  3. System settings: /etc/gemini-cli/settings.json
  4. Extensions: Internal hooks defined by installed extensions (lowest priority)

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.

{
"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/disable commands. If omitted, the command path is used as the identifier.
  • type (string, required): Hook type - currently only "command" is supported
  • command (string, required): Path to the script or command to execute
  • description (string, optional): Human-readable description shown in /hooks panel
  • timeout (number, optional): Timeout in milliseconds (default: 60000)
  • matcher (string, optional): Pattern to filter when hook runs (event matchers only)

Hooks have access to:

  • GEMINI_PROJECT_DIR: Project root directory
  • GEMINI_SESSION_ID: Current session ID
  • GEMINI_API_KEY: Gemini API key (if configured)
  • All other environment variables from the parent process

Use the /hooks panel command to view all registered hooks:

Terminal window
/hooks panel

This command displays:

  • All active hooks organized by event
  • Hook source (user, project, system)
  • Hook type (command or plugin)
  • Execution status and recent output

You can temporarily enable or disable individual hooks using commands:

Terminal window
/hooks enable hook-name
/hooks disable hook-name

These 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).

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.

If you have hooks configured for Claude Code, you can migrate them:

Terminal window
gemini hooks migrate --from-claude

This command:

  • Reads .claude/settings.json
  • Converts event names (PreToolUseBeforeTool, etc.)
  • Translates tool names (Bashrun_shell_command, replacereplace)
  • Updates matcher patterns
  • Writes to .gemini/settings.json
Claude CodeGemini CLI
PreToolUseBeforeTool
PostToolUseAfterTool
UserPromptSubmitBeforeAgent
StopAfterAgent
NotificationNotification
SessionStartSessionStart
SessionEndSessionEnd
PreCompactPreCompress
Claude CodeGemini CLI
Bashrun_shell_command
Editreplace
Readread_file
Writewrite_file
Globglob
Grepsearch_file_content
LSlist_directory

The following built-in tools can be used in BeforeTool and AfterTool hook matchers:

  • read_file - Read a single file
  • read_many_files - Read multiple files at once
  • write_file - Create or overwrite a file
  • replace - Edit file content with find/replace
  • list_directory - List directory contents
  • glob - Find files matching a pattern
  • search_file_content - Search within file contents
  • run_shell_command - Execute shell commands
  • google_web_search - Google Search with grounding
  • web_fetch - Fetch web page content
  • write_todos - Manage TODO items
  • save_memory - Save information to memory
  • delegate_to_agent - Delegate tasks to sub-agents
{
"matcher": "write_file|replace" // File editing tools
}
{
"matcher": "read_.*" // All read operations
}
{
"matcher": "run_shell_command" // Only shell commands
}
{
"matcher": "*" // All tools
}
  • startup - Fresh session start
  • resume - Resuming a previous session
  • clear - Session cleared
  • exit - Normal exit
  • clear - Session cleared
  • logout - User logged out
  • prompt_input_exit - Exit from prompt input
  • other - Other reasons
  • manual - Manually triggered compression
  • auto - Automatically triggered compression
  • ToolPermission - Tool permission notifications