🔧

Ripperdoc

Hooks

Automate workflows with custom hooks

Hooks let you execute custom scripts at various points in Ripperdoc's lifecycle.

Hook Events

EventDescription
PreToolUseBefore a tool executes
PostToolUseAfter a tool completes
PostToolUseFailureAfter a tool fails or times out
PermissionRequestWhen permission dialog is shown
UserPromptSubmitWhen user submits a message
NotificationFor status notifications
StopWhen the main agent completes
SubagentStartBefore a subagent (Task) starts
SubagentStopWhen a subagent (Task) completes
PreCompactBefore context compaction
SessionStartWhen a session begins or resumes
SessionEndWhen a session ends
SetupRepository setup/maintenance (first startup per project)

Configuration Files

Hooks are configured in JSON files (loaded in order):

  1. ~/.ripperdoc/hooks.json - Global hooks
  2. .ripperdoc/hooks.json - Project hooks
  3. .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:

FieldDescription
session_idCurrent session ID
transcript_pathPath to conversation JSON
cwdCurrent working directory
permission_modeMode: default, plan, acceptEdits, bypassPermissions
hook_event_nameName 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

DecisionEventsEffect
allowPreToolUse, PermissionRequestBypass permission, auto-approve
denyPreToolUse, PermissionRequestBlock the operation
askPreToolUsePrompt user for confirmation
blockPostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SubagentStopBlock/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 CodeBehavior
0Success. stdout processed, JSON parsed if present
2Blocking error. stderr used as error message, JSON ignored
OtherNon-blocking error. stderr shown to user

Environment Variables

Hooks receive context via environment variables:

VariableDescription
RIPPERDOC_PROJECT_DIRProject root directory
RIPPERDOC_SESSION_IDCurrent session ID
RIPPERDOC_TRANSCRIPT_PATHPath to conversation transcript JSON
RIPPERDOC_ENV_FILEPath to persist environment variables (SessionStart only)

Legacy variables (deprecated but still available):

VariableDescription
TOOL_NAMEName of the tool
TOOL_INPUTJSON-encoded tool input
FILE_PATHFile path (for file operations)
MESSAGENotification message
SESSION_IDCurrent 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 reload

Best Practices

  1. Test hooks carefully: Use logging to debug hook behavior
  2. Keep hooks fast: Slow hooks impact responsiveness (default timeout: 60s)
  3. Use local hooks for experiments: .ripperdoc/hooks.local.json is gitignored
  4. Handle errors gracefully: Exit code 0 for success, 2 for blocking errors
  5. Parse stdin JSON: Use jq or similar tools to extract input fields
  6. Use prompt hooks for complex decisions: LLM-based hooks for nuanced control