Comparing hook systems in AI coding CLIs: Gemini CLI, Claude Code, and Codex CLI
Contents
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
| Event | Timing |
|---|---|
| SessionStart | When the session starts |
| SessionEnd | When the session ends |
| BeforeAgent | Before an agent turn starts |
| AfterAgent | After an agent turn finishes |
| BeforeModel | Before sending an LLM request |
| AfterModel | After receiving an LLM response |
| BeforeToolSelection | Before tool selection |
| BeforeTool | Before tool execution |
| AfterTool | After tool execution |
| Notification | When 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
0continues, exit code2blocks BeforeModelcan 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
| Event | Timing |
|---|---|
| SessionStart | When the session starts |
| SessionEnd | When the session ends |
| UserPromptSubmit | When the user submits input |
| PreToolUse | Before tool execution |
| PostToolUse | After tool execution |
| PostToolUseFailure | When tool execution fails |
| PermissionRequest | When a permission request occurs |
| Notification | When a notification fires |
| SubagentStart | When a subagent starts |
| SubagentStop | When a subagent stops |
| Stop | When the agent stops |
| PreCompact | Before 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:
| Type | Behavior |
|---|---|
command | Run a shell command, like the other tools |
prompt | Ask an LLM to make the decision |
agent | Launch 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.
| Policy | Behavior |
|---|---|
| on-request | Ask approval for network access, out-of-workspace writes, and untrusted commands |
| untrusted | Auto-allow file edits, ask only for unknown commands |
| on-failure | Ask only when a command fails |
| never | Disable approval prompts |
Sandbox mode applies OS-level restrictions.
| Mode | Restriction |
|---|---|
| read-only | Restricts file changes and command execution |
| workspace-write | Allows edits only inside the workspace |
| danger-full-access | No 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
| Item | Gemini CLI | Claude Code | Codex CLI |
|---|---|---|---|
| Hooks | Yes | Yes | No, except notify |
| Event count | 10 | 12 | - |
| Pre-tool interception | BeforeTool | PreToolUse | Execution policy |
| Post-tool interception | AfterTool | PostToolUse | - |
| Pre-LLM interception | BeforeModel | - | - |
| LLM-based decisions | - | prompt / agent hooks | - |
| Subagent support | - | SubagentStart / SubagentStop | - |
| Sandbox | - | - | OS-level sandbox |
| Matcher | Regex | Regex | Prefix match |
| Config format | JSON | JSON | TOML + Starlark-like rules |
| Transport | stdin/stdout JSON | stdin/stdout JSON | - |
| Distribution | Extensions | - | - |
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.