Skip to main content

Overview

Hooks are scripts or programs that execute at specific points in the Gemini CLI lifecycle, allowing you to intercept and customize behavior without modifying the CLI source code. Hooks run synchronously—when a hook event fires, Gemini CLI waits for all matching hooks to complete before continuing.

Hook Events

Hooks are triggered by specific lifecycle events.

SessionStart

When: Session begins (startup, resume, or clear) Use Cases: Initialize resources, load context, set up environment Input Schema:
session_id
string
required
Unique identifier for the session
transcript_path
string
required
Path to the conversation transcript file
cwd
string
required
Current working directory
hook_event_name
string
required
Event name: "SessionStart"
timestamp
string
required
ISO 8601 timestamp
trigger
string
Session start trigger: "startup", "resume", or "clear"
Output Schema:
continue
boolean
If false, stops execution with error message from stopReason
stopReason
string
Error message shown if continue is false
systemMessage
string
Advisory message displayed to user

SessionEnd

When: Session ends (exit or clear) Use Cases: Clean up resources, save state, log summary Input Schema: Same base fields as SessionStart, plus:
trigger
string
Session end trigger: "exit" or "clear"
Output Schema: Advisory only (same as SessionStart)

BeforeAgent

When: After user submits prompt, before agent planning begins Use Cases: Add context, validate prompts, block turns Input Schema:
user_message
string
required
The user’s prompt text
conversation_history
array
Array of conversation turns (formatted for LLM)
Output Schema:
continue
boolean
If false, blocks the turn
hookSpecificOutput.additionalContext
string
Context to inject into the model’s prompt (sanitized for safety)
systemMessage
string
Message displayed to the user

AfterAgent

When: When agent loop completes Use Cases: Review output, force retry, halt execution Input Schema:
final_response
string
The agent’s final response text
Output Schema:
continue
boolean
If false, halts execution
hookSpecificOutput.retry
boolean
If true, forces the agent to retry
hookSpecificOutput.retryReason
string
Reason for retry (added to context)
systemMessage
string
Advisory message

BeforeModel

When: Before sending request to LLM Use Cases: Modify prompts, swap models, mock responses Input Schema:
llm_request
object
required
LLM request parameters
llm_request.contents
array
Conversation contents
llm_request.model
string
Model identifier
llm_request.generationConfig
object
Generation parameters (temperature, topP, etc.)
llm_request.systemInstruction
object
System instructions
Output Schema:
continue
boolean
If false, blocks the LLM request
hookSpecificOutput.llm_request
object
Modified LLM request (replaces original if provided)
hookSpecificOutput.llm_response
object
Mock LLM response (skips actual LLM call if provided)

AfterModel

When: After receiving LLM response Use Cases: Filter/redact responses, log interactions Input Schema:
llm_request
object
required
The request that was sent
llm_response
object
required
The LLM’s response
llm_response.candidates
array
Response candidates
llm_response.usageMetadata
object
Token usage statistics
Output Schema:
continue
boolean
If false, blocks the response
hookSpecificOutput.llm_response
object
Modified response (replaces original if provided)
hookSpecificOutput.redacted
boolean
If true, indicates content was redacted

BeforeToolSelection

When: Before LLM selects tools Use Cases: Filter available tools, optimize selection Input Schema:
available_tools
array
required
Array of tool declarations
tool_config
object
Tool configuration (mode, allowed functions)
Output Schema:
hookSpecificOutput.tool_config
object
Modified tool configuration
tool_config.mode
string
Tool mode: "AUTO", "ANY", or "NONE"
tool_config.allowed_function_names
string[]
Whitelist of allowed tool names
hookSpecificOutput.tools
array
Modified tools list (filters available tools)

BeforeTool

When: Before a tool executes Use Cases: Validate arguments, block dangerous operations Matcher: Regular expression matching tool names (e.g., "write_.*", "shell_tool") Input Schema:
tool_name
string
required
Name of the tool being called
tool_args
object
required
Tool arguments as key-value pairs
tool_description
string
Human-readable description of what the tool will do
Output Schema:
decision
string
Tool execution decision:
  • "allow" or "approve" - Proceed without confirmation
  • "ask" - Request user confirmation
  • "block" or "deny" - Block execution
reason
string
Reason for the decision (shown to user)
hookSpecificOutput.tool_args
object
Modified tool arguments (replaces original if provided)

AfterTool

When: After a tool executes Use Cases: Process results, run tests, hide sensitive output Matcher: Regular expression matching tool names Input Schema:
tool_name
string
required
Name of the tool that executed
tool_args
object
required
Arguments that were passed
tool_result
object
required
Tool execution result
tool_result.llmContent
string
Content sent to LLM
tool_result.returnDisplay
string
Content displayed to user
tool_result.error
object
Error details if tool failed
Output Schema:
decision
string
Result handling decision:
  • "allow" - Use the result as-is
  • "block" - Hide result from LLM and user
hookSpecificOutput.tool_result
object
Modified tool result (replaces original if provided)
hookSpecificOutput.additionalContext
string
Additional context to inject after the tool result
suppressOutput
boolean
If true, hides output from user (but sends to LLM)

PreCompress

When: Before context compression Use Cases: Save state, notify user Input Schema:
context_size
number
Current context size in tokens
max_context_size
number
Maximum context size
Output Schema: Advisory only

Notification

When: System notification occurs Use Cases: Forward to desktop alerts, logging Matcher: Exact string matching notification types (e.g., "error", "warning", "info") Input Schema:
notification_type
string
required
Notification category
message
string
required
Notification message
details
object
Additional notification details
Output Schema: Advisory only

Hook Configuration

HookConfig

Defines a single hook implementation.

Command Hook

interface CommandHookConfig {
  type: 'command';
  command: string;
  name?: string;
  description?: string;
  timeout?: number;  // milliseconds (default: 60000)
  source?: ConfigSource;
  env?: Record<string, string>;
}
type
'command'
required
Hook type (command-based hook)
command
string
required
Shell command to execute. Use $GEMINI_PROJECT_DIR and other environment variables.
name
string
Friendly name for logs and CLI commands
description
string
Brief explanation of the hook’s purpose
timeout
number
Execution timeout in milliseconds (default: 60000)
env
Record<string, string>
Additional environment variables

Runtime Hook

interface RuntimeHookConfig {
  type: 'runtime';
  name: string;
  action: HookAction;
  timeout?: number;
  source?: ConfigSource;
}

type HookAction = (
  input: HookInput,
  options?: { signal: AbortSignal }
) => Promise<HookOutput | void | null>;
type
'runtime'
required
Hook type (programmatic hook)
name
string
required
Unique identifier for the runtime hook
action
HookAction
required
Function to execute when hook is triggered

HookDefinition

Groups hooks by matcher and execution strategy.
interface HookDefinition {
  matcher?: string;
  sequential?: boolean;
  hooks: HookConfig[];
}
matcher
string
Filter pattern:
  • Tool events: Regular expression (e.g., "write_.*", "shell_.*")
  • Lifecycle events: Exact string (e.g., "startup", "error")
  • Wildcard: "*" or empty string matches all
sequential
boolean
If true, hooks run sequentially. If false (default), hooks run in parallel.
hooks
HookConfig[]
required
Array of hook configurations to execute

Configuration Example

{
  "hooks": {
    "BeforeTool": [
      {
        "matcher": "write_file|edit_file",
        "sequential": false,
        "hooks": [
          {
            "name": "security-scan",
            "type": "command",
            "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/security.sh",
            "timeout": 5000,
            "description": "Scan for security issues"
          },
          {
            "name": "format-check",
            "type": "command",
            "command": "prettier --check",
            "timeout": 3000
          }
        ]
      }
    ],
    "AfterTool": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "name": "audit-log",
            "type": "command",
            "command": "./log-tool-usage.sh"
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "name": "load-context",
            "type": "command",
            "command": "./load-project-context.sh"
          }
        ]
      }
    ]
  }
}

Exit Codes

Gemini CLI uses exit codes to determine hook execution outcomes:
0
Success
Preferred code for all logic. The stdout is parsed as JSON. Use this even for intentional blocks (set {"decision": "deny"} in output).
2
System Block
Critical block. The target action (tool, turn, or stop) is aborted. stderr is used as the rejection reason. High severity for security stops or script failures.
Other
Warning
Non-fatal failure. A warning is shown, but execution proceeds using original parameters.

Environment Variables

Hooks execute with these environment variables:
GEMINI_PROJECT_DIR
string
Absolute path to the project root
GEMINI_SESSION_ID
string
Unique ID for the current session
GEMINI_CWD
string
Current working directory
CLAUDE_PROJECT_DIR
string
Alias for GEMINI_PROJECT_DIR (compatibility)

JSON Communication

The Golden Rule

Your hook script MUST NOT print any text to stdout except the final JSON object.
#!/bin/bash

# WRONG - breaks JSON parsing
echo "Processing..."  # This goes to stdout!

# CORRECT - debug via stderr
echo "Processing..." >&2

# CORRECT - final JSON output
echo '{"decision": "allow"}'

Input (stdin)

JSON object with event-specific fields:
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.json",
  "cwd": "/workspace",
  "hook_event_name": "BeforeTool",
  "timestamp": "2026-03-03T10:30:00Z",
  "tool_name": "write_file",
  "tool_args": {
    "path": "src/index.ts",
    "content": "console.log('hello');"
  }
}

Output (stdout)

JSON object with decision and context:
{
  "decision": "allow",
  "reason": "File path is in allowed directory",
  "systemMessage": "Security check passed",
  "hookSpecificOutput": {
    "additionalContext": "Validated against security policy v2.1"
  }
}

Hook Examples

Block Dangerous Commands

#!/bin/bash
# hooks/block-dangerous.sh

input=$(cat)
command=$(echo "$input" | jq -r '.tool_args.command // ""')

if [[ "$command" =~ rm.*-rf ]]; then
  echo '{"decision": "deny", "reason": "Dangerous rm -rf command blocked"}'
  exit 0
fi

echo '{"decision": "allow"}'

Inject Git Context

#!/bin/bash
# hooks/git-context.sh

git_status=$(git status --short 2>&1)
git_branch=$(git branch --show-current 2>&1)

context="Current branch: $git_branch\n\nGit status:\n$git_status"

cat <<EOF
{
  "systemMessage": "Injected git context",
  "hookSpecificOutput": {
    "additionalContext": $(echo "$context" | jq -Rs .)
  }
}
EOF

Validate File Paths

#!/bin/bash
# hooks/validate-paths.sh

input=$(cat)
path=$(echo "$input" | jq -r '.tool_args.path // .tool_args.filePath // ""')

# Block writes outside src/ directory
if [[ "$path" != src/* ]] && [[ "$path" != "" ]]; then
  echo '{"decision": "deny", "reason": "File writes only allowed in src/ directory"}'
  exit 0
fi

echo '{"decision": "allow"}'

Run Tests After Tool

#!/bin/bash
# hooks/auto-test.sh

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')

# Run tests after file edits
if [[ "$tool_name" == "write_file" ]] || [[ "$tool_name" == "edit_file" ]]; then
  test_output=$(npm test 2>&1)
  test_exit=$?
  
  if [[ $test_exit -ne 0 ]]; then
    cat <<EOF
{
  "systemMessage": "Tests failed after file modification",
  "hookSpecificOutput": {
    "additionalContext": "Test failures:\n$(echo "$test_output" | jq -Rs .)"
  }
}
EOF
  else
    echo '{"systemMessage": "Tests passed"}'
  fi
fi

echo '{}'

Mock LLM Response

#!/bin/bash
# hooks/mock-response.sh

cat <<'EOF'
{
  "hookSpecificOutput": {
    "llm_response": {
      "candidates": [
        {
          "content": {
            "parts": [
              {
                "text": "This is a mocked response for testing."
              }
            ],
            "role": "model"
          }
        }
      ]
    }
  }
}
EOF

Managing Hooks

Use CLI commands to manage hooks:
# View all hooks
/hooks panel

# Enable/disable all
/hooks enable-all
/hooks disable-all

# Toggle individual hooks
/hooks enable security-check
/hooks disable audit-log

Security Considerations

Hooks execute arbitrary code with your user privileges. Project-level hooks are particularly risky when opening untrusted projects.

Hook Trust System

Gemini CLI fingerprints project hooks. If a hook’s name or command changes (e.g., via git pull), it’s treated as a new, untrusted hook and you’ll be warned before it executes.

Best Practices

  1. Review project hooks before trusting them
  2. Use user-level hooks for personal automation
  3. Validate all inputs in hook scripts
  4. Log hook activity for auditing
  5. Set appropriate timeouts to prevent hangs
  6. Use stderr for debugging to avoid breaking JSON output
  7. Test hooks thoroughly before deploying to teams

Configuration Sources

Hooks are merged from multiple layers in precedence order (highest to lowest):
  1. Project settings - .gemini/settings.json in current directory
  2. User settings - ~/.gemini/settings.json
  3. System settings - /etc/gemini-cli/settings.json
  4. Extensions - Hooks from installed extensions
enum ConfigSource {
  Runtime = 'runtime',    // Highest priority
  Project = 'project',
  User = 'user',
  System = 'system',
  Extensions = 'extensions' // Lowest priority
}

Build docs developers (and LLMs) love