GeezerVison is showing docs.augmentcode.com — Hooks - Augment
Overview
Hooks allow you to intercept tool execution at specific lifecycle events and run custom scripts. This enables powerful workflows like:
- Security auditing - Block dangerous commands or log sensitive file access
- Policy enforcement - Enforce coding standards or restrict production access
- Logging - Track tool usage for compliance and analytics
- Integration - Connect with external systems and workflows
Hooks execute automatically when specific events occur during agent operation, giving you fine-grained control over tool execution and session lifecycle.
Configuration
Hooks are configured in settings.json files. Augment loads hook configuration from multiple locations, allowing system-wide policies, user preferences, and project-specific hooks to coexist.
Settings File Locations
Auggie reads hooks from all of these files and runs every matching hook. Settings are listed below from highest to lowest precedence:
| Location | Platform | Supported By | Description |
|---|
/etc/augment/settings.json | Linux/macOS | CLI, VSCode, IntelliJ | System-wide settings for enterprise/admin policies (immutable) | C:\ProgramData\Augment\settings.json | Windows | CLI, VSCode, IntelliJ | System-wide settings for enterprise/admin policies (immutable) | <workspace>/.augment/settings.local.json | All | CLI | Personal project overrides — not committed to source control | <workspace>/.augment/settings.json | All | CLI | Shared project settings — committed to source control | ~/.augment/settings.json | All | CLI, VSCode, IntelliJ | User-level settings (lowest precedence) |
System-level settings (/etc/augment/settings.json) take the highest precedence and cannot be overridden by
any other configuration source. Project-scoped settings (.augment/settings.json and .augment/settings.local.json)
allow repositories to ship hook configurations that apply automatically to all contributors.
In Cosmos, Auggie also reads hooks from settings stored in the VFS.
Enabled plugins can add hooks from their hooks/hooks.json. Plugin hooks use the same format and
can refer to the plugin’s folder as ${PLUGIN_ROOT}, ${CLAUDE_PLUGIN_ROOT}, or ${AUGMENT_PLUGIN_ROOT}.
Use <workspace>/.augment/settings.json to define hooks that all contributors should use — for example,
security auditing or policy enforcement hooks. Use <workspace>/.augment/settings.local.json for personal
overrides that shouldn’t be committed to source control.
PreToolUse and PostToolUse hooks run synchronously - the agent waits for them to
complete before proceeding. Use appropriate timeouts to prevent long delays. Note that only
PreToolUse can block tool execution. Stop, SessionStart, SessionEnd, PromptSubmit, and
Notification hooks run in the background and don’t delay the agent.
Structure
Hooks are organized by event type with optional matchers:
{
"hooks": {
"PreToolUse": [
{
"matcher": "bash",
"hooks": [
{
"type": "command",
"command": "/etc/augment/hooks/validate-command.sh",
"timeout": 5000
}
]
}
]
}
}
Configuration fields:
| Field | Description |
|---|
matcher | Pattern to match tool names (case-insensitive). Supports regex patterns. Optional for PreToolUse and PostToolUse (defaults to ".*" to match all tools). Not used for other events. | hooks | Array of hook handlers to execute when the pattern matches. | type | Hook execution type - currently only "command" is supported. | command | Command to run: a script path or any command line (for example python3 ~/.augment/hooks/check.py). Run through the system shell. | args | (Optional) Argument list. When set, command is run directly with these arguments, without a shell. See Running a program with args. | timeout | (Optional) Timeout in milliseconds (default: 60000ms). | metadata | (Optional) Configuration for additional context fields - see Hook Metadata. |
Matcher pattern examples:
| Pattern | Description |
|---|
"bash" | Match a specific tool | "edit|write" | Match multiple tools using regex OR | ".*" | Match all tools | "mcp:*" | Match all MCP tools (special case) | "mcp:.*_my-server$" | Match any tool from a specific MCP server |
Only ".*" (or an omitted matcher) matches MCP tools without the mcp: prefix. A pattern such as "bash" never matches an MCP tool.
For events that don’t use matchers (SessionStart, SessionEnd, Stop, PromptSubmit, Notification):
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "/path/to/setup-script.sh"
}
]
}
]
}
}
Script Requirements
command is run through the system shell (/bin/sh on Unix, cmd.exe on Windows), so it can be a script path or any command line, such as python3 ~/.augment/hooks/check.py or node ./hooks/audit.js. There is no required file extension.
If command points directly to a script on Unix, the script must:
- Be executable - Run
chmod +x your-hook.sh
- Have a valid shebang - First line must specify the interpreter
How common script types are run:
| Extension | How it’s executed |
|---|
.sh | Executed directly on Unix; the shebang line selects the interpreter | .bat, .cmd | Dispatched to cmd.exe /c on Windows |
Shebang-based interpreter selection (Python, Node.js, Ruby, etc.) only applies to scripts run directly on Unix.
.bat/.cmd files are always run with cmd.exe — a shebang inside those files is ignored.
On Windows, .ps1 and .sh scripts are not run automatically. Put the interpreter in the command,
for example powershell.exe -File C:\hooks\check.ps1 or bash.exe ./hooks/check.sh, or use
args.
#!/usr/bin/env python3
# Python script with .sh extension on Unix - uses Python interpreter via shebang
import sys, json
event_data = json.load(sys.stdin)
# ... your Python code
Running a program with args
Set args to run command directly with a list of arguments, without a shell. Each item is passed as one argument exactly as written, so spaces and quotes need no escaping:
{
"type": "command",
"command": "node",
"args": ["${HOME}/hooks/check.js", "--strict"]
}
On Windows, .sh files run with bash.exe, .ps1 files with powershell.exe -File, and .bat/.cmd files with cmd.exe.
Environment variables in commands
Without args, the shell expands variables in command. With args, Auggie expands ${VAR} and ${VAR:-default} in command and every args item. A variable that isn’t set and has no default is left as written. Write \${ to keep a literal ${.
Hook Events
Cursor-style event names are also accepted and mapped to Auggie events: beforeShellExecution, beforeReadFile,
and beforeMCPExecution run as PreToolUse; afterShellExecution, afterFileEdit, and afterMCPExecution run
as PostToolUse; stop runs as Stop.
PreToolUse
Runs before a tool executes. Can block tool execution.
If a PreToolUse hook times out or crashes, the tool still runs. Only exit code 2 or a deny decision blocks it.
Keep security hooks fast and make them fail closed: exit with code 2 when they can’t reach a decision.
Common tool names:
| Tool | Description |
|---|
bash | Shell commands | read | File reading | edit | File editing | write | File writing | grep | Search file contents | find | Find files by glob | web-fetch | Fetch web content | web-search | Web search | codebase-retrieval | Codebase search | github-api | GitHub integration | linear | Linear integration |
Tool names changed in Auggie 1.x. Matchers that use 0.x names such as launch-process, view, str-replace-editor, or save-file still match the new tools for now, and /hooks flags them. Update matchers to the new names.
Use PreToolUse output control to block tools.
PreToolUse and PostToolUse hooks also run for tool calls made by subagents. Other events run only for the main agent.
PostToolUse
Runs immediately after a tool completes. Can provide feedback to the agent but cannot block execution.
Recognizes the same tool names as PreToolUse. Includes tool_output and tool_error in the event data.
Stop
Runs when the agent stops responding, whether from normal completion or user interrupt. The agent_stop_cause field indicates why the agent stopped (e.g., "end_turn" or "interrupted").
SessionStart
Runs when Auggie starts a new session. SessionStart hooks run in the background; their output is not added to the agent’s context. Useful for:
- Installing dependencies
- Setting up environment variables
SessionEnd
Runs when an Auggie session ends. Useful for:
- Cleanup tasks
- Logging session statistics
- Saving session state
PromptSubmit
Runs each time you submit a prompt, before the agent starts working on it. The prompt is in the user_prompt field. PromptSubmit hooks run in the background and cannot block or change the prompt.
Notification
Runs when Auggie shows a notification in interactive mode, or when a cloud agent reports an event. The notification_type and notification_message fields describe the notification. Notification hooks run in the background and cannot block anything.
Hook Input
Hooks receive event data via stdin as a JSON object. The structure varies by event type, but all events share common base fields.
Common Fields (All Events)
These fields are present in every hook event:
| Field | Type | Description |
|---|
hook_event_name | string | The type of event: "PreToolUse", "PostToolUse", "Stop", "SessionStart", "SessionEnd", "PromptSubmit", "Notification" | hook_type | string | Same value as hook_event_name | cwd | string | The session’s working directory | conversation_id | string | Unique identifier for the current conversation | workspace_roots | string[] | List of workspace root directories (usually contains one path) |
Event-Specific Fields
PreToolUse / PostToolUse Events
Tool events include information about the tool being executed:
| Field | Type | Availability | Description |
|---|
tool_name | string | Always | Name of the tool (e.g., "bash", "edit"). Use to filter which tools your hook applies to. | tool_input | object | Always | Input parameters passed to the tool. Critical for security hooks - extract and validate specific parameters. | tool_output | string? | PostToolUse only | Output returned by the tool (if successful). Use for auditing or providing context to the agent. | tool_error | string? | PostToolUse only | Error message if tool execution failed. Use to detect failures and inject troubleshooting tips. | tool_result | array | PostToolUse only | The tool’s raw result content blocks. tool_output is the text joined from these blocks. | tool_is_error | boolean | PostToolUse only | true if the tool failed. | file_changes | object[]? | PostToolUse only | File changes for write and edit. Includes path, changeType, content, and oldContent. Changes made by bash or other tools are not reported; use git status or git diff instead. | is_mcp_tool | boolean | Always | true if the tool is provided by an MCP server, false for built-in tools. |
Example: Extracting tool-specific parameters
#!/usr/bin/env bash
EVENT_DATA=$(cat)
TOOL_NAME=$(echo "$EVENT_DATA" | jq -r '.tool_name')
if [[ "$TOOL_NAME" == "bash" ]]; then
# Extract the command being executed
COMMAND=$(echo "$EVENT_DATA" | jq -r '.tool_input.command')
echo "Command: $COMMAND" >&2
# Check for dangerous patterns
if echo "$COMMAND" | grep -qE "rm -rf|sudo|curl.*sh"; then
echo "Blocked dangerous command" >&2
exit 2
fi
fi
exit 0
#!/usr/bin/env python3
import sys, json
event_data = json.load(sys.stdin)
if event_data.get('tool_name') == 'edit':
path = event_data.get('tool_input', {}).get('path', '')
if any(p in path for p in ['.env', 'secrets', 'credentials']):
print(f"[AUDIT] Sensitive file: {path}", file=sys.stderr)
sys.exit(0)
Example: Using tool_output for context (PostToolUse)
#!/usr/bin/env bash
EVENT_DATA=$(cat)
TOOL_NAME=$(echo "$EVENT_DATA" | jq -r '.tool_name')
TOOL_OUTPUT=$(echo "$EVENT_DATA" | jq -r '.tool_output // ""')
if [[ "$TOOL_NAME" == "bash" ]] && echo "$TOOL_OUTPUT" | grep -q "test.*passed"; then
# Tests passed - inject success context
cat << EOF
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "All tests passed successfully!"
}
}
EOF
fi
exit 0
#!/usr/bin/env python3
import sys
import json
event_data = json.load(sys.stdin)
tool_name = event_data.get('tool_name', '')
tool_output = event_data.get('tool_output', '')
if tool_name == 'bash' and 'test' in tool_output and 'passed' in tool_output:
# Tests passed - inject success context
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "All tests passed successfully!"
}
}
print(json.dumps(output))
sys.exit(0)
Example: Using tool_error for troubleshooting (PostToolUse)
#!/usr/bin/env bash
EVENT_DATA=$(cat)
TOOL_ERROR=$(echo "$EVENT_DATA" | jq -r '.tool_error // ""')
if [[ -n "$TOOL_ERROR" ]] && echo "$TOOL_ERROR" | grep -q "permission denied"; then
# Inject troubleshooting tip
cat << EOF
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Permission denied error detected. Try running with appropriate permissions or check file ownership."
}
}
EOF
fi
exit 0
#!/usr/bin/env python3
import sys
import json
event_data = json.load(sys.stdin)
tool_error = event_data.get('tool_error', '')
if tool_error and 'permission denied' in tool_error.lower():
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Permission denied error detected. Try with appropriate permissions."
}
}
print(json.dumps(output))
sys.exit(0)
Example: Using file_changes for audit logging (PostToolUse)
The file_changes field is populated for file-modifying tools and includes the old content for edits:
{
"hook_event_name": "PostToolUse",
"conversation_id": "conv-xyz789",
"tool_name": "edit",
"tool_input": {
"path": "src/auth.ts",
"edits": [{ "oldText": "...", "newText": "..." }]
},
"file_changes": [
{
"path": "src/auth.ts",
"changeType": "edit",
"content": "// New code content here...",
"oldContent": "// Old code that was replaced..."
}
]
}
#!/usr/bin/env bash
EVENT_DATA=$(cat)
echo "$EVENT_DATA" | jq -c '.file_changes[]?' | while read -r change; do
echo "[AUDIT] $(echo $change | jq -r '.changeType'): $(echo $change | jq -r '.path')" >&2
done
exit 0
SessionStart / SessionEnd Events
Session events have no additional fields beyond the common base fields.
Example SessionStart event:
{
"hook_event_name": "SessionStart",
"conversation_id": "conv-xyz789",
"workspace_roots": ["/Users/username/project"]
}
Stop Event
Stop events include information about why the agent stopped:
| Field | Type | Description |
|---|
agent_stop_cause | string | Why the agent stopped: "end_turn", "interrupted", "empty_completion", or "error" |
Example Stop event:
{
"hook_event_name": "Stop",
"conversation_id": "conv-xyz789",
"workspace_roots": ["/Users/username/project"],
"agent_stop_cause": "end_turn"
}
PromptSubmit Event
| Field | Type | Description |
|---|
user_prompt | string | The prompt you submitted |
Notification Event
| Field | Type | Description |
|---|
notification_type | string | Kind of notification, such as "info", "warning", or "error" | notification_message | string | The notification text |
Metadata-Based Fields
When hooks declare metadata options in their configuration, additional fields are included in the event data:
context field (when includeUserContext: true)
Available for: All event types
{
"context": {
"userEmail": "[email protected]",
"modelName": "Claude Opus 4.5",
"toolVersion": "1.2.0",
"timestamp": "2025-01-15T10:30:00-08:00"
}
}
How to use:
userEmail: Identify which user triggered the hook. Use for user-specific policies or analytics.
modelName: AI model display name (e.g., “Claude Opus 4.5”, “Sonnet-3.7”). Use for model-specific behavior.
toolVersion: CLI or VSCode extension version (e.g., “1.2.0”). Use for debugging or version-specific behavior.
timestamp: ISO 8601 timestamp. Use for auditing and analytics.
Example: User-specific permissions
#!/usr/bin/env bash
EVENT_DATA=$(cat)
USER_EMAIL=$(echo "$EVENT_DATA" | jq -r '.context.userEmail // ""')
TOOL_NAME=$(echo "$EVENT_DATA" | jq -r '.tool_name')
COMMAND=$(echo "$EVENT_DATA" | jq -r '.tool_input.command // ""')
# Only allow deployments for specific users
if [[ "$TOOL_NAME" == "bash" ]] && echo "$COMMAND" | grep -q "deploy"; then
if [[ "$USER_EMAIL" != "[email protected]" ]]; then
echo "Only admins can deploy" >&2
exit 2
fi
fi
exit 0
#!/usr/bin/env python3
import sys
import json
event_data = json.load(sys.stdin)
user_email = event_data.get('context', {}).get('userEmail', '')
# Only allow deployments for specific users
tool_name = event_data.get('tool_name', '')
command = event_data.get('tool_input', {}).get('command', '')
if tool_name == 'bash' and 'deploy' in command:
if user_email != '[email protected]':
print("Only admins can deploy", file=sys.stderr)
sys.exit(2)
sys.exit(0)
mcp_metadata field (when includeMCPMetadata: true)
Available for: PreToolUse and PostToolUse only
{
"mcp_metadata": {
"timestamp": "2025-01-15T10:30:00-08:00",
"mcpDecision": "yes",
"mcpTotalToolsCount": 215,
"mcpExecutedToolName": "search",
"mcpExecutedToolServerName": "my-server",
"mcpExecutedTotalToolsCount": 6
}
}
How to use:
timestamp: ISO 8601 timestamp. Use for auditing and analytics.
mcpDecision: Whether this is an MCP tool ("yes") or native tool ("no").
mcpTotalToolsCount: Total MCP tools available across all servers.
mcpExecutedToolName: MCP tool name as defined by the server (e.g., "search").
mcpExecutedToolServerName: MCP server name (e.g., "my-server").
mcpExecutedTotalToolsCount: Tools available from the executed server.
Example: MCP-specific rate limiting
#!/usr/bin/env bash
EVENT_DATA=$(cat)
MCP_DECISION=$(echo "$EVENT_DATA" | jq -r '.mcp_metadata.mcpDecision // "no"')
MCP_TOTAL=$(echo "$EVENT_DATA" | jq -r '.mcp_metadata.mcpTotalToolsCount // 0')
# Block if too many MCP tools are enabled (security concern)
if [[ "$MCP_DECISION" == "yes" ]] && [[ "$MCP_TOTAL" -gt 100 ]]; then
echo "Too many MCP tools enabled ($MCP_TOTAL). Contact admin." >&2
exit 2
fi
exit 0
#!/usr/bin/env python3
# /etc/augment/hooks/mcp-limit.sh (Python via shebang)
import sys
import json
event_data = json.load(sys.stdin)
mcp_metadata = event_data.get('mcp_metadata', {})
mcp_decision = mcp_metadata.get('mcpDecision', 'no')
mcp_total = mcp_metadata.get('mcpTotalToolsCount', 0)
# Block if too many MCP tools are enabled (security concern)
if mcp_decision == 'yes' and mcp_total > 100:
print(f"Too many MCP tools enabled ({mcp_total}). Contact admin.", file=sys.stderr)
sys.exit(2)
sys.exit(0)
conversation field (when includeConversationData: true)
Available for: Stop event only. On PreToolUse and PostToolUse, the same option adds only a user_prompt field with the user’s latest prompt.
{
"conversation": {
"timestamp": "2025-01-15T10:30:00-08:00",
"userPrompt": "Add error handling to the login function",
"agentTextResponse": "I'll add comprehensive error handling...",
"agentCodeResponse": [
{
"path": "src/auth/login.ts",
"changeType": "edit",
"content": "export function login() { try { ... } catch (e) { ... } }"
}
]
}
}
How to use:
timestamp: ISO 8601 timestamp. Use for auditing.
userPrompt: The user’s original request.
agentTextResponse: Agent’s explanation of what it did.
agentCodeResponse: Array of file changes. Each entry has:
path: File path modified
changeType: "edit", "create", or "delete"
content: New content (for edit/create only)
Reading Hook Input
#!/usr/bin/env bash
# Read entire JSON from stdin
EVENT_DATA=$(cat)
# Extract fields using jq
HOOK_EVENT=$(echo "$EVENT_DATA" | jq -r '.hook_event_name')
TOOL_NAME=$(echo "$EVENT_DATA" | jq -r '.tool_name // ""')
CONV_ID=$(echo "$EVENT_DATA" | jq -r '.conversation_id')
echo "Event: $HOOK_EVENT, Tool: $TOOL_NAME, Conversation: $CONV_ID"
#!/usr/bin/env python3
import sys
import json
event_data = json.load(sys.stdin)
hook_event = event_data['hook_event_name']
tool_name = event_data.get('tool_name', '')
conv_id = event_data['conversation_id']
print(f"Event: {hook_event}, Tool: {tool_name}, Conversation: {conv_id}")
Hook Output and Communication
Hooks communicate results through exit codes and output streams. The behavior depends on the exit code and the hook event type.
Exit Codes
- Exit code 0: Success - Hook completed successfully
- Exit code 2: Blocking error - Prevents tool execution (PreToolUse only)
- Other exit codes: Non-blocking error - Logged but execution continues
Output Streams
- stdout: Standard output from the hook
- stderr: Error output from the hook
Communication Matrix
The following table shows how hook output is handled based on exit code and event type:
| Exit Code | Event Type | Output Stream | Shown To | Behavior |
|---|
| 2 | PreToolUse | stderr | Agent | Blocks tool execution, agent sees why it was blocked | | 2 | SessionStart | stderr | User | Hook failed at startup, user needs to fix configuration | | 0 | PreToolUse | stderr | User | Warning message shown to user | | 0 | PreToolUse | stdout | User | Success message shown to user | | 0 | PostToolUse | stderr | User | Warning message shown to user | | 0 | PostToolUse | stdout | User | Success message shown to user | | 0 | SessionEnd | stdout | User | Show completion message | | Other | Any | stderr | User | Error logged, execution continues |
Key Principle: Exit code 2 with PreToolUse blocks the tool and shows stderr to the agent (so
it knows why). Exit code 0 shows output to the user.
PreToolUse Output
Blocking a tool (exit code 2):
#!/usr/bin/env bash
EVENT_DATA=$(cat)
COMMAND=$(echo "$EVENT_DATA" | jq -r '.tool_input.command // ""')
if echo "$COMMAND" | grep -qE "rm -rf|sudo"; then
echo "Blocked dangerous command: $COMMAND" >&2
exit 2 # Blocks tool, stderr shown to agent
fi
exit 0 # Allow tool to proceed
#!/usr/bin/env python3
import sys
import json
import re
event_data = json.load(sys.stdin)
command = event_data.get('tool_input', {}).get('command', '')
if re.search(r'rm -rf|sudo', command):
print(f"Blocked dangerous command: {command}", file=sys.stderr)
sys.exit(2) # Blocks tool, stderr shown to agent
sys.exit(0) # Allow tool to proceed
Warning message (exit code 0):
#!/usr/bin/env bash
EVENT_DATA=$(cat)
FILE_PATH=$(echo "$EVENT_DATA" | jq -r '.tool_input.path // ""')
if echo "$FILE_PATH" | grep -qE "\.env|secrets"; then
echo "Warning: Accessing sensitive file: $FILE_PATH" >&2
fi
exit 0 # Allow tool but show warning
#!/usr/bin/env python3
import sys
import json
import re
event_data = json.load(sys.stdin)
file_path = event_data.get('tool_input', {}).get('path', '')
if re.search(r'\.env|secrets', file_path):
print(f"Warning: Accessing sensitive file: {file_path}", file=sys.stderr)
sys.exit(0) # Allow tool but show warning
Hook Output Reference
Hooks can return structured output to control execution and communicate with the agent or user. Output is provided via stdout (as JSON) and stderr (for error messages).
Exit Codes
| Exit Code | Meaning | Behavior |
|---|
0 | Success | Hook completed successfully. Tool execution continues (unless JSON output blocks it). | 2 | Blocking Error | PreToolUse only: Blocks tool execution. Stderr message is shown to both user and agent. | | Other | Non-blocking Error | Hook failed, but tool execution continues. Stderr shown to the user. |
JSON Output Format
Hooks can return JSON on stdout (exit code 0 only) to provide structured control:
{
"systemMessage": "Message shown to user",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Security policy violation"
}
}
Common JSON Fields (All Events)
These fields can be returned by any hook:
| Field | Type | Description | Destination |
|---|
systemMessage | string | Warning or informational message | User |
Event-Specific JSON Output
PreToolUse Output
PreToolUse hooks can control tool execution:
| Field | Type | Description | Destination |
|---|
permissionDecision | "deny" | Block tool execution | - | permissionDecisionReason | string | Reason for blocking the tool | Agent and User | updatedInput | object | Fields to change in the tool input before it runs | Tool |
Currently only permissionDecision: "deny" is supported. The "allow" and "ask" values will be
implemented in a future release.
Example: Block dangerous command
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Command contains 'rm -rf' which is not allowed"
}
}
Example: Modify tool input
updatedInput is merged into the original tool input: fields you return replace the originals, and fields you leave out are kept. If several hooks match, each one’s changes are applied in order.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": { "command": "rm -rf ./build" }
}
}
PostToolUse Output
PostToolUse hooks can provide additional context to the agent. reason and additionalContext are appended to the tool’s result, so the agent reads them together with the tool output:
| Field | Type | Description | Destination |
|---|
decision | "block" | Sends reason to the agent. The tool has already run and is not undone; the agent is not stopped | - | reason | string | Feedback for the agent (used when decision=“block”) | Agent | additionalContext | string | Additional context for the agent to consider | Agent |
Example: Provide context to agent
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "The command modified 3 files in the authentication module"
}
}
Output Routing
Different output goes to different destinations:
To Agent (injected into conversation):
- Exit code 2 stderr (PreToolUse, PostToolUse events)
permissionDecisionReason (when decision=“deny”)
reason (when decision=“block”)
additionalContext (PostToolUse)
To User (displayed in UI):
- Exit code 2 stderr (SessionStart, SessionEnd events)
systemMessage
permissionDecisionReason (when decision=“allow” or “ask”)
- Plain (non-JSON) stdout, for non-SessionStart events
Complete Output Examples
#!/usr/bin/env bash
EVENT_DATA=$(cat)
COMMAND=$(echo "$EVENT_DATA" | jq -r '.tool_input.command // ""')
if echo "$COMMAND" | grep -qE "rm -rf"; then
echo "Blocked dangerous command: $COMMAND" >&2
exit 2 # Blocks tool, stderr shown to agent and user
fi
exit 0
#!/usr/bin/env python3
import sys
import json
import re
event_data = json.load(sys.stdin)
command = event_data.get('tool_input', {}).get('command', '')
if re.search(r'rm -rf', command):
print(f"Blocked dangerous command: {command}", file=sys.stderr)
sys.exit(2) # Blocks tool, stderr shown to agent and user
sys.exit(0)
#!/usr/bin/env bash
EVENT_DATA=$(cat)
COMMAND=$(echo "$EVENT_DATA" | jq -r '.tool_input.command // ""')
if echo "$COMMAND" | grep -qE "rm -rf"; then
cat << EOF
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Command contains 'rm -rf' which violates security policy"
}
}
EOF
exit 0
fi
exit 0
#!/usr/bin/env python3
import sys
import json
import re
event_data = json.load(sys.stdin)
command = event_data.get('tool_input', {}).get('command', '')
if re.search(r'rm -rf', command):
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Command contains 'rm -rf' which violates security policy"
}
}
print(json.dumps(output))
sys.exit(0)
sys.exit(0)
Hook Metadata (Configuration)
Hook metadata options are configuration-level flags that control what data is included in the hook input. They provide a privacy-first, opt-in model for accessing conversation data, MCP metadata, and user context.
Available Metadata Options
Metadata options are specified in the hook configuration (not in hook output):
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "/etc/augment/hooks/analytics.sh"
}
],
"metadata": {
"includeConversationData": true,
"includeMCPMetadata": true,
"includeUserContext": false
}
}
]
}
}
Metadata flags:
-
includeUserContext: Include user and environment context in hook input
- Adds
context field to hook event data
- Available for: All event types (PreToolUse, PostToolUse, Stop, SessionStart, SessionEnd)
- Fields included:
userEmail - User’s email address (e.g., “[email protected]”)
modelName - Model name being used (e.g., “Claude Opus 4.5”)
timestamp - ISO 8601 timestamp (e.g., “2025-01-15T10:30:00-08:00”)
- Default:
false (user context excluded)
-
includeMCPMetadata: Include MCP-specific metadata in hook input
- Adds
mcp_metadata field to hook event data
- Available for: PreToolUse and PostToolUse only
- Fields included:
mcpDecision - Whether MCP tools were used (“yes” | “no”)
mcpTotalToolsCount - Total number of MCP tools available across all servers
mcpExecutedToolName - Name of the executed MCP tool (if any)
mcpExecutedToolServerName - Server name of the executed MCP tool (if any)
mcpExecutedTotalToolsCount - Number of tools from the executed server (if any)
- Default:
false (MCP metadata excluded)
-
includeConversationData: Include conversation fields in hook input
- Stop: adds a
conversation field with the fields below
- PreToolUse / PostToolUse: adds a
user_prompt field with the user’s latest prompt
conversation fields:
userPrompt - The user’s prompt/message that triggered the agent response
agentTextResponse - Agent’s text response (markdown format)
agentCodeResponse - Array of file changes made by the agent, each containing:
path - File path relative to workspace root
changeType - Type of change (“edit” | “create” | “delete”)
content - File content after the change (undefined for deletions)
- Default:
false (conversation data excluded for privacy)
Metadata availability by event type:
| Metadata Option | PreToolUse | PostToolUse | Stop | SessionStart | SessionEnd |
|---|
includeUserContext | ✓ | ✓ | ✓ | ✓ | ✓ | includeMCPMetadata | ✓ | ✓ | - | - | - | includeConversationData | user_prompt only | user_prompt only | ✓ | - | - |
When to Use Metadata Options
Use includeUserContext when:
- Implementing user-specific rate limiting
- Building per-user analytics
- Tracking model usage by user
- Implementing user-based access controls
- Logging user activity for compliance
Use includeMCPMetadata when:
- Monitoring MCP tool usage patterns
- Debugging MCP integration issues
- Building MCP usage analytics
- Rate limiting MCP tool calls
- Tracking which MCP servers are being used
Use includeConversationData when:
- Building analytics dashboards that track user interactions
- Implementing compliance logging for audit trails
- Analyzing agent response quality
- Tracking code changes made by the agent
- Capturing complete conversation context for debugging
Hook Input with Metadata
When metadata options are enabled, the hook receives additional fields in the event data. Here’s an example showing all three options enabled:
Configuration:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/etc/augment/hooks/analytics.sh"
}
],
"metadata": {
"includeUserContext": true,
"includeConversationData": true
}
}
]
}
}
Hook Input (Stop event with all metadata options):
{
"hook_event_name": "Stop",
"conversation_id": "conv-xyz789",
"workspace_roots": ["/Users/username/project"],
"agent_stop_cause": "end_turn",
"context": {
"userEmail": "[email protected]",
"modelName": "Claude Opus 4.5",
"toolVersion": "1.2.0",
"timestamp": "2025-01-15T10:30:00-08:00"
},
"conversation": {
"timestamp": "2025-01-15T10:30:00-08:00",
"userPrompt": "Add error handling to the login function",
"agentTextResponse": "I'll add comprehensive error handling...",
"agentCodeResponse": [{ "path": "src/auth/login.ts", "changeType": "edit" }]
}
}
Hook Input (PostToolUse event with metadata - MCP tool):
{
"hook_event_name": "PostToolUse",
"conversation_id": "conv-xyz789",
"workspace_roots": ["/Users/username/project"],
"tool_name": "search_example-server",
"tool_input": { "query": "latest documentation" },
"tool_output": "Found 5 results...",
"is_mcp_tool": true,
"context": {
"userEmail": "[email protected]",
"modelName": "Claude Opus 4.5",
"toolVersion": "1.2.0",
"timestamp": "2025-01-15T10:30:00-08:00"
},
"mcp_metadata": {
"timestamp": "2025-01-15T10:30:00-08:00",
"mcpDecision": "yes",
"mcpTotalToolsCount": 15,
"mcpExecutedToolName": "search",
"mcpExecutedToolServerName": "example-server",
"mcpExecutedTotalToolsCount": 5
}
}
Hook Input (PostToolUse event with file_changes - file edit):
{
"hook_event_name": "PostToolUse",
"conversation_id": "conv-xyz789",
"workspace_roots": ["/Users/username/project"],
"tool_name": "edit",
"tool_input": {
"path": "src/auth.ts",
"edits": [{ "oldText": "...", "newText": "..." }]
},
"tool_output": "File edited successfully",
"is_mcp_tool": false,
"file_changes": [
{
"path": "src/auth.ts",
"changeType": "edit",
"content": "// New content...",
"oldContent": "// Old content that was replaced..."
}
],
"context": {
"userEmail": "[email protected]",
"modelName": "Claude Opus 4.5",
"toolVersion": "1.2.0",
"timestamp": "2025-01-15T10:30:00-08:00"
}
}
Privacy Considerations
Metadata options follow a privacy-first, opt-in model:
- Default deny: All sensitive data is excluded by default
- Explicit opt-in: Hooks must explicitly request metadata options
- Minimal access: Request only the options you need
- Audit trail: Metadata usage should be logged for compliance
When using metadata options that include user data (includeConversationData,
includeUserContext), ensure your hook scripts: - Handle sensitive data securely - Comply with
privacy regulations (GDPR, CCPA, etc.) - Implement appropriate data retention policies - Use
encryption for data at rest and in transit
Example: Analytics Hook with Metadata
Configuration:
{
"hooks": {
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "/etc/augment/hooks/analytics.sh"
}
],
"metadata": {
"includeUserContext": true,
"includeMCPMetadata": true
}
}
]
}
}
Hook script (with metadata options):
#!/usr/bin/env bash
# /etc/augment/hooks/analytics.sh
EVENT_DATA=$(cat)
# Extract user context (available because includeUserContext=true)
USER_EMAIL=$(echo "$EVENT_DATA" | jq -r '.context.userEmail // ""')
MODEL_NAME=$(echo "$EVENT_DATA" | jq -r '.context.modelName // ""')
# Extract MCP metadata (available because includeMCPMetadata=true)
MCP_DECISION=$(echo "$EVENT_DATA" | jq -r '.mcp_metadata.mcpDecision // "no"')
MCP_SERVER=$(echo "$EVENT_DATA" | jq -r '.mcp_metadata.mcpExecutedToolServerName // ""')
# Log analytics
echo "User: $USER_EMAIL, Model: $MODEL_NAME, MCP: $MCP_DECISION, Server: $MCP_SERVER"
exit 0
#!/usr/bin/env python3
# /etc/augment/hooks/analytics.sh (Python via shebang)
import sys
import json
event_data = json.load(sys.stdin)
# Extract user context
context = event_data.get('context', {})
user_email = context.get('userEmail', '')
model_name = context.get('modelName', '')
# Extract MCP metadata
mcp = event_data.get('mcp_metadata', {})
mcp_decision = mcp.get('mcpDecision', 'no')
mcp_server = mcp.get('mcpExecutedToolServerName', '')
print(f"User: {user_email}, Model: {model_name}, MCP: {mcp_decision}")
sys.exit(0)
Working with MCP Tools
Hooks work seamlessly with Model Context Protocol (MCP) tools. MCP tools have special naming and metadata that you can use in hooks.
MCP Tool Naming
MCP tools follow the naming pattern {toolName}_{serverName}. For example:
search_my-server - “search” tool from “my-server”
query_database-server - “query” tool from “database-server”
read_filesystem-mcp - “read” tool from “filesystem-mcp”
Both tool names and server names can contain underscores. The server name is always the suffix after the last underscore that matches the known server name. For reliable matching, enable includeMCPMetadata: true on the hook configuration and read mcp_metadata.mcpExecutedToolServerName from the event JSON rather than parsing the tool name.
MCP Tool Matchers
Use the mcp: prefix to match MCP tools. The pattern after mcp: is a regex matched against the full tool name (toolName_serverName):
"mcp:*" - Match ALL MCP tools (special case)
"mcp:.*_my-server$" - Match any tool from my-server (use $ anchor)
"mcp:^search_my-server$" - Match exact tool search from my-server
"mcp:^search_.*" - Match search tool from any server
The mcp: prefix ensures only MCP tools are matched. The pattern is a standard regex applied to
the full tool name. Use the $ anchor when matching server names to avoid partial matches.
MCP Hook Examples
Log MCP tool usage:
#!/usr/bin/env bash
EVENT_DATA=$(cat)
SERVER=$(echo "$EVENT_DATA" | jq -r '.mcp_metadata.mcpExecutedToolServerName // ""')
echo "[MCP] Server: $SERVER, Tool: $(echo "$EVENT_DATA" | jq -r '.tool_name')" >&2
exit 0
Configuration:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp:*",
"hooks": [
{
"type": "command",
"command": "/etc/augment/hooks/log-mcp-tools.sh",
"timeout": 5000
}
]
}
]
}
}
Block specific MCP server:
#!/usr/bin/env bash
EVENT_DATA=$(cat)
MCP_SERVER=$(echo "$EVENT_DATA" | jq -r '.mcp_metadata.mcpExecutedToolServerName // ""')
if [ "$MCP_SERVER" = "blocked-server" ]; then
echo "This MCP server is blocked by security policy" >&2
exit 2
fi
exit 0
Configuration:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp:.*_blocked-server$",
"hooks": [
{
"type": "command",
"command": "/etc/augment/hooks/block-mcp-server.sh",
"timeout": 5000
}
]
}
]
}
}
Examples
For ready-to-use hook examples, see Hooks Examples.
Debugging Hooks
Environment Variables
Hooks have access to these environment variables during execution:
| Variable | Description | Example |
|---|
AUGMENT_PROJECT_DIR | First workspace root directory (or process.cwd() if empty) | /Users/username/project | AUGMENT_CONVERSATION_ID | Current conversation ID | conv-xyz789 | AUGMENT_HOOK_EVENT | Event type | PreToolUse | AUGMENT_TOOL_NAME | Tool name (PreToolUse/PostToolUse only) | bash |
#!/usr/bin/env bash
echo "Project: $AUGMENT_PROJECT_DIR, Event: $AUGMENT_HOOK_EVENT" >&2
exit 0
Testing Hooks Locally
1. Create a test event file:
cat > test-event.json << 'EOF'
{
"hook_event_name": "PreToolUse",
"conversation_id": "test-conv",
"workspace_roots": ["/Users/username/project"],
"tool_name": "bash",
"tool_input": { "command": "git status" }
}
EOF
2. Test your hook script:
# Test your hook script (works for both bash and Python via shebang)
cat test-event.json | /etc/augment/hooks/my-hook.sh
echo "Exit code: $?"
3. Validate JSON output:
# Test and validate JSON output
OUTPUT=$(cat test-event.json | /etc/augment/hooks/my-hook.sh)
echo "$OUTPUT" | jq . # Validates JSON syntax
Viewing Hook Execution Logs
Hooks log to the Augment logger. To see hook execution details:
CLI:
# Run with debug-level logging
auggie --log-level debug "your prompt here"
Use --log-file <path> to choose where the log is written. Hook log lines use the pi-hooks prefix.
Run /hooks in interactive mode to see the configured hooks and any configuration problems.
Common Debugging Techniques
Log to stderr (won’t affect hook output):
#!/usr/bin/env bash
EVENT_DATA=$(cat)
echo "[DEBUG] $(echo "$EVENT_DATA" | jq -r '.hook_event_name')" >&2
exit 0
Save event data for inspection:
#!/usr/bin/env bash
cat > /tmp/hook-debug.json
exit 0
3. Validate hook matcher patterns
To test if your matcher pattern works:
# Test regex pattern in bash
TOOL_NAME="search_my-server"
PATTERN=".*_my-server$"
if [[ "$TOOL_NAME" =~ $PATTERN ]]; then
echo "Pattern matches!"
else
echo "Pattern does not match"
fi
# Test regex pattern in Python
import re
tool_name = "search_my-server"
pattern = r".*_my-server$"
if re.match(pattern, tool_name):
print("Pattern matches!")
else:
print("Pattern does not match")
Performance Tips
- Keep hooks fast - Hooks run synchronously and block tool execution
- Cache expensive operations - Store results in files or environment variables
- Use early returns - Exit as soon as you know the result
- Avoid network calls - Network requests add latency
- Minimize JSON parsing - Parse only the fields you need
Example: Fast hook with early return
#!/usr/bin/env bash
EVENT_DATA=$(cat)
# Early return if not a bash tool
TOOL_NAME=$(echo "$EVENT_DATA" | jq -r '.tool_name')
if [[ "$TOOL_NAME" != "bash" ]]; then
exit 0 # Not interested, return immediately
fi
# Only parse command if we got here
COMMAND=$(echo "$EVENT_DATA" | jq -r '.tool_input.command')
# Check command
if echo "$COMMAND" | grep -qE "rm -rf"; then
echo "Blocked dangerous command" >&2
exit 2
fi
exit 0
#!/usr/bin/env python3
import sys
import json
import re
event_data = json.load(sys.stdin)
# Early return if not a bash tool
tool_name = event_data.get('tool_name', '')
if tool_name != 'bash':
sys.exit(0) # Not interested, return immediately
# Only parse command if we got here
command = event_data.get('tool_input', {}).get('command', '')
# Check command
if re.search(r'rm -rf', command):
print("Blocked dangerous command", file=sys.stderr)
sys.exit(2)
sys.exit(0)
Troubleshooting
Check:
- Matcher pattern - Verify your regex pattern matches the tool name
- Event type - Ensure you’re listening to the correct event (PreToolUse vs PostToolUse)
- Settings file location - Verify hooks are in the correct settings file (project vs user vs system)
- Hook path - Ensure the hook script path is correct and the file exists
- File permissions - Make sure the hook script is executable (
chmod +x hook.sh)
Debug:# Check if hook file exists and is executable
ls -la /etc/augment/hooks/my-hook.sh
# Test matcher pattern
echo "bash" | grep -E "bash|edit" # Should match
echo "read" | grep -E "bash|edit" # Should not match
Hooks have a 60-second timeout by default. If your hook takes longer:
- Optimize the hook - Make it faster
- Run async operations - Don’t wait for slow operations
- Use background jobs - Start long-running tasks in the background
#!/usr/bin/env bash
EVENT_DATA=$(cat)
# Start long-running task in background (don't wait)
(
# Long-running operation here
sleep 300
) &
# Return immediately
exit 0
Common issues:
- Invalid JSON syntax - Use
jq to validate
- Missing quotes - Ensure all strings are quoted
- Trailing commas - JSON doesn’t allow trailing commas
- Special characters - Escape special characters in strings
Validate JSON:# Test JSON output
cat << 'EOF' | jq .
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny"
}
}
EOF
Hook blocks but shouldn't
Check:
- Exit code - Make sure you’re returning
exit 0 for success
- JSON output - Verify
permissionDecision is not set to "deny"
- stderr output - Ensure you’re not writing errors to stderr with exit code 2
Debug:#!/usr/bin/env bash
EVENT_DATA=$(cat)
# Explicitly allow
echo "Allowing tool execution" >&2 # Debug message
exit 0 # Success - don't block
Check the Auggie logs for hook execution errors:# Write logs to a known file, then watch it from another terminal
auggie --log-level debug --log-file /tmp/auggie.log
tail -f /tmp/auggie.log | grep -i hook
Test your hook script manually:# Create test event data
echo '{
"hook_event_name": "PreToolUse",
"tool_name": "bash",
"tool_input": {"command": "ls -la"}
}' | /etc/augment/hooks/your-hook.sh
# Check exit code
echo $?
Security Considerations
USE AT YOUR OWN RISK: Hooks execute arbitrary shell commands on your system automatically. By using hooks, you acknowledge that:
- You are solely responsible for the commands you configure
- Hooks can modify, delete, or access any files your user account can access
- Malicious or poorly written hooks can cause data loss or system damage
- You should thoroughly test hooks in a safe environment before production use
Always review and understand any hook commands before adding them to your configuration.
Security Best Practices
- Validate and sanitize inputs - Never trust input data blindly
- Always quote shell variables - Use
"$VAR" not $VAR
- Block path traversal - Check for
.. in file paths
- Use absolute paths - Specify full paths for scripts
- Skip sensitive files - Avoid
.env, .git/, keys, etc.
- Set appropriate timeouts - Prevent hooks from hanging indefinitely
- Test thoroughly - Test hooks with various inputs before deploying
Best Practices
- Keep hooks fast: Hooks should complete quickly (< 1 second) to avoid slowing down the agent
- Use timeouts: Always set reasonable timeouts to prevent hanging
- Handle errors gracefully: Use exit code 0 for non-critical errors to avoid blocking the agent
- Log appropriately: Use stderr for user-facing messages, log files for detailed audit trails
- Test thoroughly: Test hooks with various tool inputs before deploying
- Version control: Keep hook scripts in version control with your project
- Document behavior: Add comments explaining what each hook does and why
Limitations
- Hooks currently only support command execution (webhooks planned for future)
- PostToolUse hooks cannot modify tool output (read-only)
- Hooks cannot access the agent’s conversation history directly
- PreToolUse and PostToolUse hooks run one at a time, in order; if a hook blocks the tool, later hooks for that call do not run. Other events run their hooks at the same time
Hook Execution Details
- Timeout: 60-second execution limit by default, configurable per command
- Execution order: Hooks execute in the order they are defined
- Environment: Runs in current directory with Auggie’s environment
- Input: JSON via stdin
- Output:
- PreToolUse/PostToolUse/Stop: Progress shown in logs
- SessionStart: not added to the agent’s context
- SessionEnd: Logged to debug only
Related Documentation
|