Tech 6 min read

Comparing hook systems in AI coding CLIs: Gemini CLI, Claude Code, and Codex CLI

IkesanContents

I compared the hook systems in the three major AI coding CLIs: Gemini CLI, Claude Code, and Codex CLI. The trigger was Google’s official blog post introducing hooks in Gemini CLI.

Once I looked into them, it became clear that the three tools have very different design philosophies.

What Are Hooks?

In AI coding CLIs, hooks are a way to inject custom logic into the agent lifecycle, such as before or after tool execution or at session start and end. They are similar to middleware in web frameworks.

Typical use cases:

  • Validate before tool execution, for example blocking dangerous commands
  • Prevent secret leakage, for example scanning before file writes
  • Collect logs or optimize token cost
  • Inject project-specific context when a session starts

Gemini CLI

Google introduced Gemini CLI hooks on its official blog in July 2025. They are available in v0.26.0 and later.

Configuration

They are configured in .gemini/settings.json.

{
  "hooks": {
    "BeforeTool": [
      {
        "matcher": "write_file|replace",
        "hooks": [
          {
            "name": "secret-scanner",
            "type": "command",
            "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/scan-secrets.sh",
            "description": "Scan for secrets before writing files"
          }
        ]
      }
    ]
  }
}

Events

EventTiming
SessionStartWhen the session starts
SessionEndWhen the session ends
BeforeAgentBefore an agent turn starts
AfterAgentAfter an agent turn finishes
BeforeModelBefore sending an LLM request
AfterModelAfter receiving an LLM response
BeforeToolSelectionBefore tool selection
BeforeToolBefore tool execution
AfterToolAfter tool execution
NotificationWhen a notification fires

That is 10 event types. The standout ones are BeforeModel and BeforeToolSelection. Gemini CLI is the only one of the three that can intervene in the model request itself.

Communication protocol

  • Hooks receive JSON on stdin and return JSON on stdout
  • Exit code 0 continues, exit code 2 blocks
  • BeforeModel can even return a mock response, which skips the LLM call
  • Default timeout is 60 seconds, but it is configurable

Configuration layering

Settings are merged across project, user, and system levels. Gemini CLI also allows hooks to be bundled and distributed through Extensions, which reflects its ecosystem-first approach.

Claude Code

Claude Code also has a hook system.

Configuration

Hooks are configured in .claude/settings.json.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.claude/hooks/validate-bash.py"
          }
        ]
      }
    ]
  }
}

Events

EventTiming
SessionStartWhen the session starts
SessionEndWhen the session ends
UserPromptSubmitWhen the user submits input
PreToolUseBefore tool execution
PostToolUseAfter tool execution
PostToolUseFailureWhen tool execution fails
PermissionRequestWhen a permission request occurs
NotificationWhen a notification fires
SubagentStartWhen a subagent starts
SubagentStopWhen a subagent stops
StopWhen the agent stops
PreCompactBefore context compaction

That makes 12 event types, the most among the three. SubagentStart and SubagentStop are especially distinctive because they reflect a design that assumes multi-agent workflows.

Hook types: the biggest differentiator

Claude Code hooks support three type values:

TypeBehavior
commandRun a shell command, like the other tools
promptAsk an LLM to make the decision
agentLaunch an LLM agent and delegate work

The prompt and agent types are unique to Claude Code. The hook logic itself can be delegated to an LLM. For example, instead of rule-based logic, you can ask the model whether a Bash command looks safe.

That is flexible, but it introduces a tradeoff in both cost and latency because the hook itself may trigger model calls.

Configuration layering

Claude Code uses three layers: user, project, and local.

Codex CLI

Codex CLI does not have a formal hook system. There is a community request for one in GitHub Discussions (#2150), but it has not been implemented.

The closest thing it has: notify

# ~/.codex/config.toml
notify = ["say", "Codex is done"]

This runs an external command when the agent completes a turn. It is only for notifications, not for intercepting tool execution before or after the fact.

What it does instead

Instead of hooks, Codex CLI takes a declarative approach to controlling tool execution.

Approval policy controls when the user is asked for confirmation.

PolicyBehavior
on-requestAsk approval for network access, out-of-workspace writes, and untrusted commands
untrustedAuto-allow file edits, ask only for unknown commands
on-failureAsk only when a command fails
neverDisable approval prompts

Sandbox mode applies OS-level restrictions.

ModeRestriction
read-onlyRestricts file changes and command execution
workspace-writeAllows edits only inside the workspace
danger-full-accessNo restrictions

On macOS it uses Seatbelt, on Linux Landlock plus seccomp, and on Windows a WSL sandbox. That feels very characteristic of Codex CLI.

Execution policy uses command-pattern rules in .rules.

prefix_rule(pattern="git status", decision="allow")
prefix_rule(pattern="rm -rf /", decision="forbidden")

The three outcomes are allow, prompt, and forbidden. If multiple rules apply, the most restrictive one wins.

Comparison table

ItemGemini CLIClaude CodeCodex CLI
HooksYesYesNo, except notify
Event count1012-
Pre-tool interceptionBeforeToolPreToolUseExecution policy
Post-tool interceptionAfterToolPostToolUse-
Pre-LLM interceptionBeforeModel--
LLM-based decisions-prompt / agent hooks-
Subagent support-SubagentStart / SubagentStop-
Sandbox--OS-level sandbox
MatcherRegexRegexPrefix match
Config formatJSONJSONTOML + Starlark-like rules
Transportstdin/stdout JSONstdin/stdout JSON-
DistributionExtensions--

Differences in design philosophy

Looking across the three tools, the differences in philosophy are pretty clear.

Gemini CLI goes deepest into the stack. It can intercept LLM requests themselves with BeforeModel, filter tool choice with BeforeToolSelection, and hook into the agent’s internal flow at a low level. The ability to package hooks as Extensions also shows an ecosystem-oriented design.

Claude Code is the only one that can delegate the hook’s own decision logic to an LLM. Instead of writing rigid rule-based hooks for ambiguous questions like “is this command safe?”, it lets you push that judgment back onto the model. The presence of SubagentStart and SubagentStop also suggests a design shaped by integration with the Agent SDK and multi-agent workflows.

Codex CLI does not have hooks and instead relies on declarative policy plus OS-level sandboxing. Rather than allowing arbitrary script-based interception, it aims to provide safer defaults by enforcing security at the operating-system level. The philosophy feels less like “make everything customizable in code” and more like “make the default behavior safe.”

Which approach is best depends on the use case, but the one I found most interesting personally was Gemini CLI’s BeforeModel. Being able to intercept requests and responses directly feels powerful for testing, debugging, and cost control.

References