Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.qoder.com/llms.txt

Use this file to discover all available pages before exploring further.

Hooks allow you to intercept the Agent’s main execution flow at key points in Qoder CLI, while remaining decoupled from the CLI itself. Common use cases include: blocking dangerous operations before tool execution, sending desktop notifications after a task completes, automatically running lint after writing files, and more. Hooks are defined via JSON configuration files — no code changes required. Simply edit the config file and they take effect immediately.

Quick Start

The following example demonstrates how to use a Hook to block dangerous commands — automatically preventing execution when the Agent attempts to run rm -rf. Step 1: Create the script
mkdir -p ~/.qoder/hooks
cat > ~/.qoder/hooks/block-rm.sh << 'EOF'
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')

if echo "$command" | grep -q 'rm -rf'; then
  echo "Dangerous command blocked: $command" >&2
  exit 2
fi

exit 0
EOF
chmod +x ~/.qoder/hooks/block-rm.sh
Step 2: Edit the configuration file Add the following to ~/.qoder/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.qoder/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
Step 3: Verify Start Qoder CLI and ask the Agent to run a command containing rm -rf. The Hook will block the execution and notify the Agent.

Configuration

Configuration File Locations

Hook configuration is loaded from the following three files. All three levels are merged and executed together:
~/.qoder/settings.json                    # User-level, applies to all projects
${project}/.qoder/settings.json           # Project-level, applies to the current project; can be committed to git for team sharing
${project}/.qoder/settings.local.json     # Project-level (local), highest priority; recommended to add to .gitignore

Configuration Format

{
  "hooks": {
    "EventName": [
      {
        "matcher": "match condition",
        "hooks": [
          {
            "type": "command",
            "command": "command to execute",
            "timeout": 60
          }
        ]
      }
    ]
  }
}
Field descriptions:
FieldRequiredDescription
typeYesFixed value: "command"
commandYesThe shell command to execute
timeoutNoTimeout in seconds (default: 60)
matcherNoMatch condition; matches all if omitted
Multiple matcher groups can be configured under a single event, and each group can contain multiple hook commands.

Matcher Rules

matcher filters the scope of hook triggers. Different events match different fields (see individual event descriptions).
SyntaxMeaningExample
Omitted or "*"Match allAll tools trigger
Exact valueExact match"Bash" matches only the Bash tool
| separatedMatch multiple values"Write|Edit" matches Write or Edit
Regular expressionRegex match"mcp__.*" matches all MCP tools

Writing Hook Scripts

Hook scripts receive JSON input via stdin and control behavior through exit codes and stdout. This section describes the common input/output format for all events. Additional fields specific to each event are described in Supported Events.

Input

Hook scripts receive JSON data via stdin. All events include the following common fields:
FieldDescription
session_idCurrent session ID
cwdCurrent working directory
hook_event_nameName of the triggered event
Different events append additional fields on top of these (see individual event descriptions). Parse input using jq:
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')

Output

Hooks control behavior through exit codes and stdout. Exit code determines the basic behavior: 0 for success, 2 for blocking (stderr content is injected into the conversation, only effective for events that support blocking), other values are non-blocking errors. stdout JSON (parsed only when exit code is 0) provides fine-grained control for some events — see individual event descriptions for supported fields. stdout is ignored when exit code is non-zero.

Environment Variables

The following environment variables are available when hook scripts execute:
VariableDescription
QODER_PROJECT_DIRWorking directory of the current project

Supported Events

Qoder CLI supports the following Hook events, covering all stages of the session lifecycle.

SessionStart

Triggered when a session starts. matcher field: Session source
matcher valueTrigger scenario
startupNew session started
resumeExisting session resumed
compactAfter context compaction completes
Additional input fields:
{
  "source": "startup",
  "model": "Auto"
}

SessionEnd

Triggered when a session ends. matcher field: End reason
matcher valueTrigger scenario
prompt_input_exitUser exits input (Ctrl+D, etc.)
otherOther reasons
Additional input fields:
{
  "reason": "prompt_input_exit"
}

UserPromptSubmit

Triggered after the user submits a prompt, before the Agent begins processing it. Additional input fields:
{
  "prompt": "Write a sorting function for me"
}

PreToolUse

Triggered before tool execution. Can block tool execution. matcher field: Tool name (e.g. Bash, Write, Edit, Read, Glob, Grep; MCP tool names like mcp__server__tool) Additional input fields:
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf /tmp/build"},
  "tool_use_id": "toolu_01ABC123"
}
Blocking tool execution: exit code 2; stderr content is returned to the Agent as an error. For a complete example, see Quick Start.

PostToolUse

Triggered after a tool executes successfully. matcher field: Tool name Additional input fields:
{
  "tool_name": "Write",
  "tool_input": {"file_path": "/path/to/file.ts", "content": "..."},
  "tool_response": "File written successfully",
  "tool_use_id": "toolu_01ABC123"
}

PostToolUseFailure

Triggered after a tool execution fails. matcher field: Tool name Additional input fields:
{
  "tool_name": "Bash",
  "tool_input": {"command": "npm test"},
  "tool_use_id": "toolu_01ABC123",
  "error": "Command exited with non-zero status code 1",
  "is_interrupt": false
}

Stop

Triggered when the Agent finishes responding (main Agent, with no pending tool calls). Can prevent the Agent from stopping and let it continue working. Preventing the Agent from stopping: exit code 2; stderr content is injected into the conversation as a message, and the Agent continues working.

SubagentStart / SubagentStop

Triggered when a sub-agent starts and completes. SubagentStop is similar to Stop and can prevent the sub-agent from stopping. matcher field: Agent type name Additional input fields:
{
  "agent_id": "a1b2c3d4",
  "agent_type": "task"
}

PreCompact

Triggered before context compaction. matcher field: Trigger method
matcher valueTrigger scenario
manualUser manually runs /compact
autoAutomatically triggered when context window is full
Additional input fields:
{
  "trigger": "manual",
  "custom_instructions": "Preserve all tool call results"
}

Notification

Triggered on notification events (permission requests, task completion, etc.). matcher field: Notification type
matcher valueTrigger scenario
permissionPermission request notification
resultAgent result notification
Additional input fields:
{
  "message": "Agent is requesting permission to run: rm -rf node_modules",
  "title": "Permission Required",
  "notification_type": "permission"
}

PermissionRequest

Triggered when a tool requires user authorization to execute. matcher field: Tool name Additional input fields:
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf node_modules"}
}

Practical Examples

Desktop Notifications

Pop up a desktop notification when the Agent completes a task or requires authorization. Script ~/.qoder/hooks/notify.sh (macOS):
#!/bin/bash
input=$(cat)
message=$(echo "$input" | jq -r '.message')

if echo "$message" | grep -q "^Agent"; then
  osascript -e 'display notification "Task completed" with title "Qoder CLI"'
else
  osascript -e 'display notification "Task requires authorization" with title "Qoder CLI"'
fi

exit 0
Configuration:
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.qoder/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}

Auto-Lint After Writing Files

Automatically run lint checks every time the Agent writes or edits a file. Script ${project}/.qoder/hooks/auto-lint.sh:
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

# Only check JS/TS files
case "$file_path" in
  *.js|*.ts|*.jsx|*.tsx)
    npx eslint "$file_path" --fix 2>/dev/null
    ;;
esac

exit 0
Configuration: event PostToolUse, matcher Write|Edit, command .qoder/hooks/auto-lint.sh.

Keep the Agent Working

When the Agent stops, check whether there are unfinished tasks; if so, inject a message to keep the Agent working. Script ~/.qoder/hooks/check-continue.sh:
#!/bin/bash
# Check for uncommitted git changes
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
  echo "Uncommitted changes detected, please complete git commit" >&2
  exit 2
fi

exit 0
Configuration: event Stop, command ~/.qoder/hooks/check-continue.sh.