All skills
Skillintermediate

Gemini CLI Hook System: Exhaustive Reference

This document provides an exhaustive reference for the Gemini CLI hook system, covering configuration, JSON schemas, tool names, edge cases, version differences, and comparisons with Claude Code.

Claude Code Knowledge Pack7/10/2026

Overview

Gemini CLI Hook System: Exhaustive Reference

This document provides an exhaustive reference for the Gemini CLI hook system, covering configuration, JSON schemas, tool names, edge cases, version differences, and comparisons with Claude Code.

Last Updated: January 2026 Gemini CLI Versions Covered: v0.15.x through v0.23.x


Table of Contents

  1. Overview
  2. Settings File Locations
  3. Hook Configuration JSON Schema
  4. Hook Events
  5. Tool Names
  6. Input JSON Schema
  7. Output JSON Schema
  8. Exit Code Behavior
  9. Decision Field Values
  10. Matcher Patterns
  11. Configuration Merge Strategies
  12. Environment Variables
  13. CLI Commands for Hook Management
  14. Claude Code Migration
  15. Gemini CLI vs Claude Code Comparison
  16. Version History and Changes
  17. Known Bugs and Issues
  18. Edge Cases and Quirks
  19. Best Practices
  20. Security Considerations
  21. Sources and References

Overview

Gemini CLI hooks are scripts or programs executed at specific points in the agentic loop, allowing interception and customization of behavior without modifying CLI source code.

Key Characteristics:

  • Hooks run synchronously within the agent loop
  • Communication uses JSON over stdin/stdout with exit code signaling
  • Hooks mirror the contract used by Claude Code for compatibility
  • The system was developed starting in late 2024 and matured through 2025

Sources:


Settings File Locations

Configuration Hierarchy (Lowest to Highest Precedence)

PriorityLocationDescription
1Hardcoded defaultsBuilt into Gemini CLI
2System defaultsOS-specific location (see below)
3User settings~/.gemini/settings.json
4Project settings.gemini/settings.json in project root
5System overrides/etc/gemini-cli/settings.json
6Environment variablesRuntime overrides
7Command-line argumentsHighest priority

System Default Locations by OS

OSPath
Linux/etc/gemini-cli/system-defaults.json
macOS/Library/Application Support/GeminiCli/system-defaults.json
WindowsC:\ProgramData\gemini-cli\system-defaults.json

The path can be overridden using GEMINI_CLI_SYSTEM_DEFAULTS_PATH environment variable.

Format Migration (September 2025)

  • 09/10/25: New nested JSON format supported in stable release
  • 09/17/25: Automatic migration from old format began
  • Legacy v1 configuration documented separately

Source: Gemini CLI Configuration


Hook Configuration JSON Schema

Basic Structure

{
  "hooks": {
    "EventName": [
      {
        "matcher": "pattern",
        "hooks": [
          {
            "name": "unique-hook-identifier",
            "type": "command",
            "command": "./path/to/script.sh",
            "description": "Human-readable description",
            "timeout": 30000
          }
        ]
      }
    ],
    "disabled": ["hook-name-1", "hook-name-2"]
  }
}

Hook Object Properties

PropertyTypeRequiredDefaultDescription
namestringRecommendedCommand pathUnique identifier for enable/disable commands
typestringYes-Hook type; currently only "command" supported
commandstringYes-Path to script; supports $GEMINI_PROJECT_DIR
descriptionstringNo-Shown in /hooks panel
timeoutnumberNo60000Timeout in milliseconds
matcherstringNo-Pattern to filter when hook runs

Extension Hooks (v0.21.0+)

Extensions can include a hooks/hooks.json file:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "WriteFile",
        "hooks": [
          {
            "type": "command",
            "command": "${extensionPath}/scripts/lint.sh"
          }
        ]
      }
    ]
  }
}

Extension hooks support ${extensionPath} variable substitution.

Source: GitHub Issue #14449: Hook Support in Extensions


Hook Events

Complete Event List

EventTrigger PointMatcher Applies To
SessionStartSession initializationstartup, resume, clear
SessionEndSession terminationexit, clear, logout, prompt_input_exit, other
BeforeAgentBefore planning phaseN/A
AfterAgentAfter agent loop completesN/A
BeforeModelBefore LLM requestN/A
AfterModelAfter LLM responseN/A
BeforeToolSelectionPre-tool filteringN/A
BeforeToolBefore tool executionTool names
AfterToolAfter tool executionTool names
PreCompressBefore context compressionmanual, auto
NotificationPermission/notification eventsToolPermission

Event Capabilities

EventCan BlockCan Modify InputCan Inject Context
BeforeToolYesYes (via deny + reason)Yes
AfterToolYesN/AYes (additionalContext)
BeforeAgentYesN/AYes (additionalContext)
AfterAgentYesN/AN/A
BeforeModelYesYes (llm_request)N/A
AfterModelYesYes (llm_response)N/A
BeforeToolSelectionNoYes (toolConfig)N/A
SessionStartNoN/AYes (additionalContext)

Source: Hooks Reference


Tool Names

Core Tool Names for Matchers

CategoryTool Names
File Operationsread_file, read_many_files, write_file, replace
File Systemlist_directory, glob, search_file_content
Shell Executionrun_shell_command
Web/Externalgoogle_web_search, web_fetch
Agent Featureswrite_todos, save_memory, delegate_to_agent

MCP Tool Name Format

MCP tools follow the pattern: mcp__<server_name>__<tool_name>

Example: mcp__github__create_issue

Shell Tool Details (run_shell_command)

The shell tool executes commands with platform-specific behavior:

PlatformExecution Method
Windowspowershell.exe -NoProfile -Command
Linux/macOSbash -c

Configuration options:

{
  "tools": {
    "shell": {
      "enableInteractiveShell": true,
      "showColor": true,
      "pager": "cat"
    }
  }
}

When run_shell_command executes, it sets GEMINI_CLI=1 environment variable in subprocesses.

Source: Shell Tool Documentation


Input JSON Schema

Base Fields (All Events)

Every hook receives these fields via stdin:

{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/path/to/project",
  "hook_event_name": "BeforeTool",
  "timestamp": "2025-12-01T10:30:00Z"
}

Tool Events (BeforeTool, AfterTool)

{
  "session_id": "...",
  "transcript_path": "...",
  "cwd": "...",
  "hook_event_name": "BeforeTool",
  "timestamp": "...",
  "tool_name": "write_file",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": { },
  "mcp_context": {
    "server_name": "github",
    "tool_name": "create_issue",
    "command": "...",
    "args": [],
    "cwd": "...",
    "url": "...",
    "tcp": "..."
  }
}

MCP Context Transport Types:

  • stdio: command, args, cwd
  • SSE/HTTP: url
  • WebSocket: tcp

Agent Events (BeforeAgent, AfterAgent)

{
  "session_id": "...",
  "hook_event_name": "BeforeAgent",
  "prompt": "User's submitted text",
  "prompt_response": "Final model response",
  "stop_hook_active": true
}

Model Events (BeforeModel, AfterModel, BeforeToolSelection)

{
  "session_id": "...",
  "hook_event_name": "BeforeModel",
  "llm_request": {
    "model": "gemini-2.0-flash",
    "messages": [
      { "role": "user", "content": "..." },
      { "role": "model", "content": "..." }
    ],
    "config": {
      "temperature": 0.7,
      "maxOutputTokens": 8192,
      "topP": 0.95,
      "topK": 40
    },
    "toolConfig": {
      "mode": "AUTO",
      "allowedFunctionNames": ["read_file", "write_file"]
    }
  },
  "llm_response": { }
}

Session Events

SessionStart:

{
  "hook_event_name": "SessionStart",
  "source": "startup"
}

Values: "startup" | "resume" | "clear"

SessionEnd:

{
  "hook_event_name": "SessionEnd",
  "reason": "exit"
}

Values: "exit" | "clear" | "logout" | "prompt_input_exit" | "other"

Notification Events

{
  "hook_event_name": "Notification",
  "notification_type": "ToolPermission",
  "message": "...",
  "details": { }
}

Output JSON Schema

Common Output Fields

{
  "decision": "allow",
  "reason": "Explanation for agent",
  "systemMessage": "Message for user",
  "continue": true,
  "stopReason": "User-facing termination message",
  "suppressOutput": false,
  "hookSpecificOutput": { }
}

hookSpecificOutput by Event

SessionStart, BeforeAgent, AfterTool:

{
  "hookSpecificOutput": {
    "hookEventName": "AfterTool",
    "additionalContext": "Extra context appended to agent"
  }
}

BeforeModel:

{
  "hookSpecificOutput": {
    "llm_request": { },
    "llm_response": { }
  }
}

AfterModel:

{
  "hookSpecificOutput": {
    "llm_response": { }
  }
}

BeforeToolSelection:

{
  "hookSpecificOutput": {
    "toolConfig": {
      "mode": "ANY",
      "allowedFunctionNames": ["read_file", "write_file"]
    }
  }
}

Stable LLMRequest Object

{
  "model": "string",
  "messages": [
    {
      "role": "user | model | system",
      "content": "string or array of parts"
    }
  ],
  "config": {
    "temperature": 0.7,
    "maxOutputTokens": 8192,
    "topP": 0.95,
    "topK": 40
  },
  "toolConfig": {
    "mode": "AUTO | ANY | NONE",
    "allowedFunctionNames": ["tool1", "tool2"]
  }
}

Stable LLMResponse Object

{
  "text": "string",
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": ["string"]
      },
      "finishReason": "STOP | MAX_TOKENS | SAFETY | RECITATION | OTHER",
      "index": 0,
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_...",
          "probability": "NEGLIGIBLE | LOW | MEDIUM | HIGH",
          "blocked": false
        }
      ]
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 100,
    "candidatesTokenCount": 200,
    "totalTokenCount": 300
  }
}

Note: In v1, model hooks are primarily text-focused. Non-text parts in the content array are simplified to string representation.


Exit Code Behavior

Exit CodeBehaviorstdout Handling
0SuccessParsed as JSON; falls back to systemMessage if invalid
2Blocking errorstderr shown to agent/user; operation may be blocked
OtherNon-blocking warningstderr logged but execution continues

Exit Code Semantics Comparison

AspectGemini CLIClaude Code
Success00
Blocking22
WarningNon-zero (except 2)Non-zero

Source: Hooks Reference


Decision Field Values

Available Decision Values

ValueEffectUse Case
allowOperation proceedsExplicitly permit action
denyOperation blockedBlock with reason shown to agent
blockOperation blockedSimilar to deny
askPrompt userRequest user confirmation
approveApprove pendingApprove a previously asked operation

Decision Examples

Deny a tool execution:

{
  "decision": "deny",
  "reason": "Potential secret detected in file content"
}

**Allow with