Hooks
Automate workflows with custom hooks
Hooks let you execute custom scripts at various points in Ripperdoc's lifecycle.
Hook Events
| Event | Description |
|---|---|
PreToolUse | Before a tool executes |
PostToolUse | After a tool completes |
PostToolUseFailure | After a tool fails or times out |
PermissionRequest | When permission dialog is shown |
UserPromptSubmit | When user submits a message |
Notification | For status notifications |
Stop | When the main agent completes |
SubagentStart | Before a subagent (Task) starts |
SubagentStop | When a subagent (Task) completes |
PreCompact | Before context compaction |
SessionStart | When a session begins or resumes |
SessionEnd | When a session ends |
Setup | Repository setup/maintenance (first startup per project) |
Configuration Files
Hooks are configured in JSON files (loaded in order):
~/.ripperdoc/hooks.json- Global hooks.ripperdoc/hooks.json- Project hooks.ripperdoc/hooks.local.json- Local hooks (gitignored)
Configuration Format
The correct configuration format uses nested event/matcher structure:
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
]
}
]
}
}Basic Example
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'Bash completed' >> /tmp/ripperdoc.log",
"timeout": 60
}
]
}
]
}
}Hook Types
Command Hooks
Execute shell commands:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'Tool completed'",
"timeout": 60
}
]
}
]
}
}Prompt Hooks
Use an LLM to process the hook (supported for Stop, SubagentStop, UserPromptSubmit, PreToolUse, PermissionRequest):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "prompt",
"prompt": "Review this edit for security issues: $ARGUMENTS",
"timeout": 30
}
]
}
]
}
}Matchers
The matcher field controls when hooks run:
Applicable events: PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure.
- Exact tool name:
"Bash","Edit","Write" - Regex pattern:
"Edit|Write","mcp__.*__write.*" - Match all:
"*"or omit the matcher field
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "echo 'File operation detected'"
}
]
}
]
}
}Hook Input (stdin JSON)
Hooks receive context as JSON via stdin. Common fields for all hooks:
| Field | Description |
|---|---|
session_id | Current session ID |
transcript_path | Path to conversation JSON |
cwd | Current working directory |
permission_mode | Mode: default, plan, acceptEdits, bypassPermissions |
hook_event_name | Name of the event |
PreToolUse/PostToolUse Input
{
"hook_event_name": "PreToolUse",
"session_id": "abc123",
"cwd": "/path/to/project",
"tool_name": "Bash",
"tool_input": {"command": "npm test"},
"tool_use_id": "tool_123"
}PostToolUseFailure Input
Runs when a tool execution errors or times out.
{
"hook_event_name": "PostToolUseFailure",
"session_id": "abc123",
"cwd": "/path/to/project",
"tool_name": "Bash",
"tool_input": {"command": "npm test"},
"tool_response": null,
"tool_error": "Tool 'Bash' timed out after 300 seconds",
"tool_use_id": "tool_123"
}PermissionRequest Input
{
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": {"command": "rm -rf temp"},
"tool_use_id": "tool_456"
}UserPromptSubmit Input
{
"hook_event_name": "UserPromptSubmit",
"prompt": "Please fix the bug in main.py"
}Stop/SubagentStop Input
{
"hook_event_name": "Stop",
"stop_hook_active": false,
"reason": "end_turn"
}SubagentStart Input
{
"hook_event_name": "SubagentStart",
"subagent_type": "codebase-explorer",
"prompt": "Inspect the hooks system and summarize gaps",
"resume": null,
"run_in_background": false,
"tool_use_id": "tool_789"
}PreCompact Input
{
"hook_event_name": "PreCompact",
"trigger": "auto",
"custom_instructions": ""
}The trigger field can be manual (from /compact command) or auto (automatic compaction).
SessionStart Input
{
"hook_event_name": "SessionStart",
"source": "startup"
}The source field can be: startup, resume, clear, compact.
Setup Input
{
"hook_event_name": "Setup",
"session_id": "abc123",
"cwd": "/path/to/project"
}Setup runs once per project on the first SessionStart with source: "startup".
SessionEnd Input
{
"hook_event_name": "SessionEnd",
"reason": "other",
"duration_seconds": 123.45,
"message_count": 10
}Notification Input
{
"hook_event_name": "Notification",
"message": "Permission required",
"notification_type": "permission_prompt"
}Notification types: permission_prompt, idle_prompt, auth_success, elicitation_dialog.
Hook Output (JSON)
Hooks can output JSON to control execution:
{
"decision": "allow",
"reason": "Approved by hook"
}Decision Values
| Decision | Events | Effect |
|---|---|---|
allow | PreToolUse, PermissionRequest | Bypass permission, auto-approve |
deny | PreToolUse, PermissionRequest | Block the operation |
ask | PreToolUse | Prompt user for confirmation |
block | PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SubagentStop | Block/prevent stopping |
Advanced Output Format
{
"continue": true,
"stopReason": "Optional reason",
"suppressOutput": false,
"systemMessage": "Message to add to system prompt",
"additionalContext": "Context to inject",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Auto-approved by policy",
"updatedInput": {"command": "modified command"}
}
}Exit Codes
| Exit Code | Behavior |
|---|---|
0 | Success. stdout processed, JSON parsed if present |
2 | Blocking error. stderr used as error message, JSON ignored |
| Other | Non-blocking error. stderr shown to user |
Environment Variables
Hooks receive context via environment variables:
| Variable | Description |
|---|---|
RIPPERDOC_PROJECT_DIR | Project root directory |
RIPPERDOC_SESSION_ID | Current session ID |
RIPPERDOC_TRANSCRIPT_PATH | Path to conversation transcript JSON |
RIPPERDOC_ENV_FILE | Path to persist environment variables (SessionStart only) |
Legacy variables (deprecated but still available):
| Variable | Description |
|---|---|
TOOL_NAME | Name of the tool |
TOOL_INPUT | JSON-encoded tool input |
FILE_PATH | File path (for file operations) |
MESSAGE | Notification message |
SESSION_ID | Current session ID |
Examples
Logging Tool Usage
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date)] Tool executed\" >> ~/.ripperdoc/tool.log"
}
]
}
]
}
}Protect Sensitive Files
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 -c \"import sys, json; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); sys.exit(2) if '.env' in path else sys.exit(0)\" 2>&1 || echo '{\"decision\": \"deny\", \"reason\": \"Cannot edit .env files\"}'"
}
]
}
]
}
}Desktop Notifications
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "jq -r '.message' | xargs -I {} notify-send 'Ripperdoc' '{}'"
}
]
}
]
}
}Auto-format on Edit
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path // empty' | grep '\\.py$' | xargs -r black 2>/dev/null || true"
}
]
}
]
}
}Notify on Tool Failure
{
"hooks": {
"PostToolUseFailure": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_name + \" failed: \" + (.tool_error // \"unknown\")' | xargs -I {} notify-send 'Ripperdoc' '{}'"
}
]
}
]
}
}Block Agent Stop
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if the task is complete. If not, respond with {\"decision\": \"block\", \"reason\": \"Continue working on the task\"}",
"timeout": 30
}
]
}
]
}
}Initialize Environment on Session Start
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo 'MY_VAR=value' >> \"$RIPPERDOC_ENV_FILE\""
}
]
}
]
}
}Managing Hooks
Use the /hooks command:
> /hooks list
> /hooks reloadBest Practices
- Test hooks carefully: Use logging to debug hook behavior
- Keep hooks fast: Slow hooks impact responsiveness (default timeout: 60s)
- Use local hooks for experiments:
.ripperdoc/hooks.local.jsonis gitignored - Handle errors gracefully: Exit code 0 for success, 2 for blocking errors
- Parse stdin JSON: Use
jqor similar tools to extract input fields - Use prompt hooks for complex decisions: LLM-based hooks for nuanced control