Skip to content

Commit 1050c67

Browse files
committed
create documentation for event key input field mappings and usage
1 parent 191df51 commit 1050c67

File tree

2 files changed

+462
-3
lines changed

2 files changed

+462
-3
lines changed

docs/cli/hooks.md

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
# Hooks
2+
3+
Hooks allow you to execute custom shell scripts at specific points in the Gemini
4+
CLI lifecycle. This enables powerful customizations like audit logging, context
5+
injection, tool filtering, and integration with external systems.
6+
7+
## Quick Start
8+
9+
### 1. Enable Hooks
10+
11+
In `~/.gemini/settings.json`:
12+
13+
```json
14+
{
15+
"tools": {
16+
"enableHooks": true
17+
}
18+
}
19+
```
20+
21+
### 2. Configure a Hook
22+
23+
```json
24+
{
25+
"hooks": {
26+
"SessionStart": [
27+
{
28+
"hooks": [
29+
{
30+
"type": "command",
31+
"command": "~/.gemini/hooks/my-hook.sh",
32+
"timeout": 10000
33+
}
34+
]
35+
}
36+
]
37+
}
38+
}
39+
```
40+
41+
### 3. Create the Hook Script
42+
43+
```bash
44+
#!/bin/bash
45+
# ~/.gemini/hooks/my-hook.sh
46+
47+
# Read JSON input from stdin
48+
INPUT=$(cat)
49+
50+
# Parse event name
51+
EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')
52+
53+
# Log to file
54+
echo "[$(date)] Event: $EVENT" >> /tmp/gemini_hooks.log
55+
56+
# Output valid JSON
57+
echo '{"continue": true}'
58+
```
59+
60+
Make it executable:
61+
62+
```bash
63+
chmod +x ~/.gemini/hooks/my-hook.sh
64+
```
65+
66+
## Hook Events
67+
68+
| Event | When It Fires | Key Input Fields |
69+
| --------------------- | --------------------------------- | ------------------------------------------ |
70+
| `SessionStart` | CLI starts up, resumes, or clears | `source` |
71+
| `SessionEnd` | CLI exits | `reason` |
72+
| `BeforeAgent` | Before processing user prompt | `prompt` |
73+
| `AfterAgent` | After agent responds | `prompt`, `prompt_response` |
74+
| `BeforeTool` | Before a tool executes | `tool_name`, `tool_input` |
75+
| `AfterTool` | After a tool completes | `tool_name`, `tool_input`, `tool_response` |
76+
| `PreCompress` | Before context compression | `trigger` |
77+
| `BeforeModel` | Before LLM API call | `llm_request` |
78+
| `AfterModel` | After LLM API response | `llm_request`, `llm_response` |
79+
| `BeforeToolSelection` | Before tool selection | `llm_request` |
80+
81+
## Hook Input Format
82+
83+
All hooks receive JSON via stdin with these common fields:
84+
85+
```json
86+
{
87+
"session_id": "abc123...",
88+
"transcript_path": "/path/to/session.json",
89+
"cwd": "/current/working/directory",
90+
"hook_event_name": "BeforeAgent",
91+
"timestamp": "2025-01-15T10:30:00.000Z"
92+
}
93+
```
94+
95+
Plus event-specific fields (see table above).
96+
97+
## Hook Output Format
98+
99+
Hooks should output JSON to stdout:
100+
101+
```json
102+
{
103+
"continue": true,
104+
"decision": "allow",
105+
"reason": "Optional reason text",
106+
"systemMessage": "Message to display to user"
107+
}
108+
```
109+
110+
### Exit Codes
111+
112+
| Code | Meaning |
113+
| ---- | -------------------------- |
114+
| `0` | Success |
115+
| `1` | Warning (non-blocking) |
116+
| `2` | Blocking error (deny/stop) |
117+
118+
## Configuration Structure
119+
120+
```json
121+
{
122+
"hooks": {
123+
"[EventName]": [
124+
{
125+
"hooks": [
126+
{
127+
"type": "command",
128+
"command": "path/to/script.sh",
129+
"timeout": 60000,
130+
"enabled": true
131+
}
132+
],
133+
"matcher": "pattern",
134+
"sequential": false
135+
}
136+
]
137+
}
138+
}
139+
```
140+
141+
### Fields
142+
143+
| Field | Type | Description |
144+
| ------------ | ------- | ---------------------------------------- |
145+
| `type` | string | Always `"command"` |
146+
| `command` | string | Path to script or shell command |
147+
| `timeout` | number | Timeout in milliseconds (default: 60000) |
148+
| `enabled` | boolean | Enable/disable this hook (default: true) |
149+
| `matcher` | string | Pattern for tool-related hooks (regex) |
150+
| `sequential` | boolean | Run hooks sequentially vs parallel |
151+
152+
## Tool Name Reference
153+
154+
For `BeforeTool` and `AfterTool` hooks, use these tool names in matchers:
155+
156+
| Tool | Name in Gemini CLI |
157+
| ----------------- | --------------------- |
158+
| File editing | `replace` |
159+
| File writing | `write_file` |
160+
| File reading | `read_file` |
161+
| Shell commands | `run_shell_command` |
162+
| Todo lists | `write_todos` |
163+
| File globbing | `glob` |
164+
| Content search | `search_file_content` |
165+
| Directory listing | `list_directory` |
166+
| Web fetch | `web_fetch` |
167+
| Web search | `google_web_search` |
168+
169+
### Matcher Examples
170+
171+
```json
172+
{
173+
"AfterTool": [
174+
{
175+
"hooks": [
176+
{ "type": "command", "command": "~/.gemini/hooks/log-edits.sh" }
177+
],
178+
"matcher": "replace|write_file"
179+
},
180+
{
181+
"hooks": [
182+
{ "type": "command", "command": "~/.gemini/hooks/log-shell.sh" }
183+
],
184+
"matcher": "run_shell_command"
185+
}
186+
]
187+
}
188+
```
189+
190+
## Migrating from Claude Code
191+
192+
### Automatic Migration
193+
194+
```bash
195+
gemini hooks migrate
196+
```
197+
198+
This command:
199+
200+
1. Reads your `~/.claude/settings.json`
201+
2. Transforms event names and tool matchers
202+
3. Converts timeouts (seconds → milliseconds)
203+
4. Updates script paths (`.claude/hooks``.gemini/hooks`)
204+
5. Saves to `~/.gemini/settings.json`
205+
206+
### Event Name Mapping
207+
208+
| Claude Code | Gemini CLI |
209+
| ------------------ | -------------- |
210+
| `UserPromptSubmit` | `BeforeAgent` |
211+
| `PostToolUse` | `AfterTool` |
212+
| `Stop` | `AfterAgent` |
213+
| `PreCompact` | `PreCompress` |
214+
| `SessionStart` | `SessionStart` |
215+
| `SessionEnd` | `SessionEnd` |
216+
217+
### Tool Name Mapping
218+
219+
| Claude Code | Gemini CLI |
220+
| ----------- | --------------------- |
221+
| `Edit` | `replace` |
222+
| `MultiEdit` | `replace` |
223+
| `Write` | `write_file` |
224+
| `Read` | `read_file` |
225+
| `Bash` | `run_shell_command` |
226+
| `TodoWrite` | `write_todos` |
227+
| `Glob` | `glob` |
228+
| `Grep` | `search_file_content` |
229+
230+
### Field Name Differences
231+
232+
| Concept | Claude Code | Gemini CLI |
233+
| ---------- | ----------- | --------------------- |
234+
| User input | `.message` | `.prompt` |
235+
| Event name | N/A | `.hook_event_name` |
236+
| Tool name | Same | Different (see table) |
237+
238+
### Compatibility Shim
239+
240+
For existing Claude Code scripts, use the compatibility shim:
241+
242+
```bash
243+
#!/bin/bash
244+
source ~/.gemini/hooks/utils/claude-compat-shim.sh
245+
246+
# Now use normalized variables:
247+
echo "User message: $USER_MESSAGE"
248+
echo "Session ID: $SESSION_ID"
249+
echo "Event: $HOOK_EVENT_NAME"
250+
echo "Tool (normalized): $TOOL_NAME_NORMALIZED"
251+
```
252+
253+
The shim provides:
254+
255+
- `$USER_MESSAGE` - Works with both `.message` (Claude) and `.prompt` (Gemini)
256+
- `$TOOL_NAME_NORMALIZED` - Maps Gemini tool names to Claude-style names
257+
- Helper functions: `hook_success()`, `hook_block()`, `hook_warn()`
258+
259+
## Examples
260+
261+
### Audit Logger
262+
263+
Log all tool executions:
264+
265+
```bash
266+
#!/bin/bash
267+
# ~/.gemini/hooks/audit-logger.sh
268+
269+
INPUT=$(cat)
270+
EVENT=$(echo "$INPUT" | jq -r '.hook_event_name')
271+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // "N/A"')
272+
273+
echo "[$(date)] $EVENT - Tool: $TOOL" >> /tmp/gemini_audit.log
274+
echo '{"continue": true}'
275+
```
276+
277+
### Context Injector
278+
279+
Add project context to prompts:
280+
281+
```bash
282+
#!/bin/bash
283+
# ~/.gemini/hooks/inject-context.sh
284+
285+
INPUT=$(cat)
286+
287+
# Read project context
288+
if [ -f ".gemini/context.md" ]; then
289+
CONTEXT=$(cat .gemini/context.md)
290+
echo "{\"continue\": true, \"hookSpecificOutput\": {\"additionalContext\": \"$CONTEXT\"}}"
291+
else
292+
echo '{"continue": true}'
293+
fi
294+
```
295+
296+
### Tool Guardian
297+
298+
Block dangerous commands:
299+
300+
```bash
301+
#!/bin/bash
302+
# ~/.gemini/hooks/tool-guard.sh
303+
304+
INPUT=$(cat)
305+
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
306+
307+
# Block rm -rf commands
308+
if echo "$TOOL_INPUT" | grep -q 'rm.*-rf'; then
309+
echo '{"decision": "block", "reason": "Dangerous rm -rf command blocked"}'
310+
exit 2
311+
fi
312+
313+
echo '{"continue": true}'
314+
```
315+
316+
## Hook Commands
317+
318+
Manage hooks with these CLI commands:
319+
320+
```bash
321+
# List all configured hooks
322+
gemini hooks list
323+
324+
# Disable a hook
325+
gemini hooks disable "~/.gemini/hooks/my-hook.sh"
326+
327+
# Enable a disabled hook
328+
gemini hooks enable "~/.gemini/hooks/my-hook.sh"
329+
330+
# Migrate from Claude Code
331+
gemini hooks migrate
332+
```
333+
334+
## Debugging
335+
336+
### Test Your Hook
337+
338+
```bash
339+
# Manually test with sample input
340+
echo '{"hook_event_name": "BeforeAgent", "prompt": "test", "session_id": "123", "cwd": "/tmp", "timestamp": "2025-01-01T00:00:00Z"}' | ~/.gemini/hooks/my-hook.sh
341+
```
342+
343+
### View Hook Execution
344+
345+
Check the debug log for hook execution details:
346+
347+
```bash
348+
# Enable debug mode
349+
export DEBUG=gemini:*
350+
351+
# Or check hook output in your script
352+
tail -f /tmp/gemini_hooks.log
353+
```
354+
355+
### Common Issues
356+
357+
1. **Hook not firing**: Ensure `tools.enableHooks: true` in settings
358+
2. **"UnknownEvent" logged**: Read event name from JSON stdin, not command args
359+
3. **Permission denied**: Make script executable with `chmod +x`
360+
4. **Timeout errors**: Increase `timeout` value or optimize script
361+
5. **Matcher not working**: Use Gemini tool names (e.g., `replace` not `Edit`)
362+
363+
## Environment Variables
364+
365+
Hooks have access to these environment variables:
366+
367+
| Variable | Description |
368+
| ----------------------------------------- | ------------------------- |
369+
| `GEMINI_PROJECT_DIR` | Current working directory |
370+
| `CLAUDE_PROJECT_DIR` | Same (for compatibility) |
371+
| Plus all existing `process.env` variables |

0 commit comments

Comments
 (0)