TerminaI hooks
Hooks are scripts or programs that TerminaI executes at specific points in the agentic loop, allowing you to intercept and customize behavior without modifying the CLI’s source code.
See writing hooks guide for a tutorial on creating your first hook and a comprehensive example.
See hooks reference for the technical specification of the I/O schemas.
See best practices for guidelines on security, performance, and debugging.
What are hooks?
With hooks, you can:
- Add context: Inject relevant information before the model processes a request
- Validate actions: Review and block potentially dangerous operations
- Enforce policies: Implement security and compliance requirements
- Log interactions: Track tool usage and model responses
- Optimize behavior: Dynamically adjust tool selection or model parameters
Hooks run synchronously as part of the agent loop—when a hook event fires, TerminaI waits for all matching hooks to complete before continuing.
Core concepts
Hook events
Hooks are triggered by specific events in TerminaI’s lifecycle. The following table lists all available hook events:
Event
When It Fires
Common Use Cases
SessionStart
When a session begins
Initialize resources, load context
SessionEnd
When a session ends
Clean up, save state
BeforeAgent
After user submits prompt, before planning
Add context, validate prompts
AfterAgent
When agent loop ends
Review output, force continuation
BeforeModel
Before sending request to LLM
Modify prompts, add instructions
AfterModel
After receiving LLM response
Filter responses, log interactions
BeforeToolSelection
Before LLM selects tools (after BeforeModel)
Filter available tools, optimize selection
BeforeTool
Before a tool executes
Validate arguments, block dangerous ops
AfterTool
After a tool executes
Process results, run tests
PreCompress
Before context compression
Save state, notify user
Notification
When a notification occurs (e.g., permission)
Auto-approve, log decisions
Hook types
TerminaI currently supports command hooks that run shell commands or scripts:
{ "type": "command", "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/my-hook.sh", "timeout": 30000}
Note: Plugin hooks (npm packages) are planned for a future release.
Matchers
For tool-related events (BeforeTool, AfterTool), you can filter which tools trigger the hook:
{ "hooks": { "BeforeTool": [ { "matcher": "write_file|replace", "hooks": [ /* hooks for write operations */ ] } ] }}
Matcher patterns:
- Exact match:
"read_file"matches onlyread_file - Regex:
"write_.*|replace"matcheswrite_file,replace - Wildcard:
"*"or""matches all tools
Session event matchers:
- SessionStart:
startup,resume,clear - SessionEnd:
exit,clear,logout,prompt_input_exit - PreCompress:
manual,auto - Notification:
ToolPermission
Hook input/output contract
Command hook communication
Hooks communicate via:
- Input: JSON on stdin
- Output: Exit code + stdout/stderr
Exit codes
- 0: Success - stdout shown to user (or injected as context for some events)
- 2: Blocking error - stderr shown to agent/user, operation may be blocked
- Other: Non-blocking warning - logged but execution continues
Common input fields
Every hook receives these base fields:
{ "session_id": "abc123", "transcript_path": "/path/to/transcript.jsonl", "cwd": "/path/to/project", "hook_event_name": "BeforeTool", "timestamp": "2025-12-01T10:30:00Z" // ... event-specific fields}
Event-specific fields
BeforeTool
Input:
{ "tool_name": "write_file", "tool_input": { "file_path": "/path/to/file.ts", "content": "..." }}
Output (JSON on stdout):
{ "decision": "allow|deny|ask|block", "reason": "Explanation shown to agent", "systemMessage": "Message shown to user"}
Or simple exit codes:
- Exit 0 = allow (stdout shown to user)
- Exit 2 = deny (stderr shown to agent)
AfterTool
Input:
{ "tool_name": "read_file", "tool_input": { "file_path": "..." }, "tool_response": "file contents..."}
Output:
{ "decision": "allow|deny", "hookSpecificOutput": { "hookEventName": "AfterTool", "additionalContext": "Extra context for agent" }}
BeforeAgent
Input:
{ "prompt": "Fix the authentication bug"}
Output:
{ "decision": "allow|deny", "hookSpecificOutput": { "hookEventName": "BeforeAgent", "additionalContext": "Recent project decisions: ..." }}
BeforeModel
Input:
{ "llm_request": { "model": "gemini-2.0-flash-exp", "messages": [{ "role": "user", "content": "Hello" }], "config": { "temperature": 0.7 }, "toolConfig": { "functionCallingConfig": { "mode": "AUTO", "allowedFunctionNames": ["read_file", "write_file"] } } }}
Output:
{ "decision": "allow", "hookSpecificOutput": { "hookEventName": "BeforeModel", "llm_request": { "messages": [ { "role": "system", "content": "Additional instructions..." }, { "role": "user", "content": "Hello" } ] } }}
AfterModel
Input:
{ "llm_request": { "model": "gemini-2.0-flash-exp", "messages": [ /* ... */ ], "config": { /* ... */ }, "toolConfig": { /* ... */ } }, "llm_response": { "text": "string", "candidates": [ { "content": { "role": "model", "parts": ["array of content parts"] }, "finishReason": "STOP" } ] }}
Output:
{ "hookSpecificOutput": { "hookEventName": "AfterModel", "llm_response": { "candidate": { /* modified response */ } } }}
BeforeToolSelection
Input:
{ "llm_request": { "model": "gemini-2.0-flash-exp", "messages": [ /* ... */ ], "toolConfig": { "functionCallingConfig": { "mode": "AUTO", "allowedFunctionNames": [ /* 100+ tools */ ] } } }}
Output:
{ "hookSpecificOutput": { "hookEventName": "BeforeToolSelection", "toolConfig": { "functionCallingConfig": { "mode": "ANY", "allowedFunctionNames": ["read_file", "write_file", "replace"] } } }}
Or simple output (comma-separated tool names sets mode to ANY):
Terminal window
echo "read_file,write_file,replace"
SessionStart
Input:
{ "source": "startup|resume|clear"}
Output:
{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "Loaded 5 project memories" }}
SessionEnd
Input:
{ "reason": "exit|clear|logout|prompt_input_exit|other"}
No structured output expected (but stdout/stderr logged).
PreCompress
Input:
{ "trigger": "manual|auto"}
Output:
{ "systemMessage": "Compression starting..."}
Notification
Input:
{ "notification_type": "ToolPermission", "message": "string", "details": { /* notification details */ }}
Output:
{ "systemMessage": "Notification logged"}
Configuration
Hook definitions are configured in settings.json files using the hooks object. Configuration can be specified at multiple levels with defined precedence rules.
Configuration layers
Hook configurations are applied in the following order of execution (lower numbers run first):
- Project settings:
.gemini/settings.jsonin your project directory (highest priority) - User settings:
~/.terminai/settings.json - System settings:
/etc/terminai/settings.json - Extensions: Internal hooks defined by installed extensions (lowest priority)
Deduplication and shadowing
If multiple hooks with the identical name and command are discovered across different configuration layers, TerminaI deduplicates them. The hook from the higher-priority layer (e.g., Project) will be kept, and others will be ignored.
Within each level, hooks run in the order they are declared in the configuration.
Configuration schema
{ "hooks": { "EventName": [ { "matcher": "pattern", "hooks": [ { "name": "hook-identifier", "type": "command", "command": "./path/to/script.sh", "description": "What this hook does", "timeout": 30000 } ] } ] }}
Configuration properties:
name(string, recommended): Unique identifier for the hook used in/hooks enable/disablecommands. If omitted, thecommandpath is used as the identifier.type(string, required): Hook type - currently only"command"is supportedcommand(string, required): Path to the script or command to executedescription(string, optional): Human-readable description shown in/hooks paneltimeout(number, optional): Timeout in milliseconds (default: 60000)matcher(string, optional): Pattern to filter when hook runs (event matchers only)
Environment variables
Hooks have access to:
GEMINI_PROJECT_DIR: Project root directoryGEMINI_SESSION_ID: Current session IDGEMINI_API_KEY: LLM API key (if configured)- All other environment variables from the parent process
Managing hooks
View registered hooks
Use the /hooks panel command to view all registered hooks:
Terminal window
/hooks panel
This command displays:
- All active hooks organized by event
- Hook source (user, project, system)
- Hook type (command or plugin)
- Execution status and recent output
Enable and disable hooks
You can temporarily enable or disable individual hooks using commands:
Terminal window
/hooks enable hook-name/hooks disable hook-name
These commands allow you to control hook execution without editing configuration files. The hook name should match the name field in your hook configuration. Changes made via these commands are persisted to your global User settings (~/.terminai/settings.json).
Disabled hooks configuration
To permanently disable hooks, add them to the hooks.disabled array in your settings.json:
{ "hooks": { "disabled": ["secret-scanner", "auto-test"] }}
Note: The hooks.disabled array uses a UNION merge strategy. Disabled hooks from all configuration levels (user, project, system) are combined and deduplicated, meaning a hook disabled at any level remains disabled.
Migration from Claude Code
If you have hooks configured for Claude Code, you can migrate them:
Terminal window
gemini hooks migrate --from-claude
This command:
- Reads
.claude/settings.json - Converts event names (
PreToolUse→BeforeTool, etc.) - Translates tool names (
Bash→run_shell_command,replace→replace) - Updates matcher patterns
- Writes to
.gemini/settings.json
Event name mapping
Claude Code
TerminaI
PreToolUse
BeforeTool
PostToolUse
AfterTool
UserPromptSubmit
BeforeAgent
Stop
AfterAgent
Notification
Notification
SessionStart
SessionStart
SessionEnd
SessionEnd
PreCompact
PreCompress
Tool name mapping
Claude Code
TerminaI
Bash
run_shell_command
Edit
replace
Read
read_file
Write
write_file
Glob
glob
Grep
search_file_content
LS
list_directory
Tool and Event Matchers Reference
Available tool names for matchers
The following built-in tools can be used in BeforeTool and AfterTool hook matchers:
File operations
read_file- Read a single fileread_many_files- Read multiple files at oncewrite_file- Create or overwrite a filereplace- Edit file content with find/replace
File system
list_directory- List directory contentsglob- Find files matching a patternsearch_file_content- Search within file contents
Execution
run_shell_command- Execute shell commands
Web and external
google_web_search- Google Search with groundingweb_fetch- Fetch web page content
Agent features
write_todos- Manage TODO itemssave_memory- Save information to memorydelegate_to_agent- Delegate tasks to sub-agents
Example matchers
{ "matcher": "write_file|replace" // File editing tools}
{ "matcher": "read_.*" // All read operations}
{ "matcher": "run_shell_command" // Only shell commands}
{ "matcher": "*" // All tools}
Event-specific matchers
SessionStart event matchers
startup- Fresh session startresume- Resuming a previous sessionclear- Session cleared
SessionEnd event matchers
exit- Normal exitclear- Session clearedlogout- User logged outprompt_input_exit- Exit from prompt inputother- Other reasons
PreCompress event matchers
manual- Manually triggered compressionauto- Automatically triggered compression
Notification event matchers
ToolPermission- Tool permission notifications
Learn more
- Writing Hooks - Tutorial and comprehensive example
- Best Practices - Security, performance, and debugging
- Custom Commands - Create reusable prompt shortcuts
- Configuration - TerminaI configuration options