Skip to main content
Guides

Hooks

Hooks run custom scripts at key points in the Qoder CN CLI agent lifecycle—such as blocking dangerous commands, sending notifications on task completion, or auto-linting written files. Hooks are defined in a JSON configuration file and take effect immediately—no code changes required.

Quick start

This example blocks the Agent from running rm -rf commands. Step 1: Create the script
mkdir -p ~/.qoder-cn/hooks

cat > ~/.qoder-cn/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-cn/hooks/block-rm.sh
Step 2: Edit the configuration file Add the following to ~/.qoder-cn/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.qoder-cn/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
Step 3: Verify the result Start Qoder CN CLI and ask the Agent to run a command containing rm -rf. The hook blocks execution and notifies the Agent.

Configuration

Configuration file locations

Hook configurations are loaded from the following three files. The settings from all three levels are merged and executed:
~/.qoder-cn/settings.json                    # User-level: applies to all projects.
${project}/.qoder/settings.json           # Project-level: applies to the current project. Can be committed to Git to share with your team.
${project}/.qoder/settings.local.json     # Project-level (local): has the highest priority. Recommended to add to .gitignore.

Configuration format

{
  "hooks": {
    "EventName": [
      {
        "matcher": "match condition",
        "hooks": [
          {
            "type": "command",
            "command": "command to run",
            "timeout": 60
          }
        ]
      }
    ]
  }
}
Fields:
FieldRequiredDescription
typeYesMust be set to "command".
commandYesThe shell command to run.
timeoutNoThe timeout in seconds. Default: 60.
matcherNoMatch condition. If omitted, the hook applies to all instances.
A single event supports multiple matcher groups, each containing multiple hook commands.

Matcher rules

The matcher field determines when a hook triggers. Each event matches against a different field.

Syntax

Description

Example

Leave blank or "&#42;"

Matches all.

Triggers for all tools.

Exact value

Exact match.

"Bash" matches only the Bash tool.

| delimiter

Matches multiple values.

"Write|Edit" matches the Write or Edit tool.

Regular expression

Regular expression match.

"mcp&#95;&#95;.&#42;" matches all MCP tools

Writing hook scripts

Hook scripts receive a JSON payload on stdin and use exit codes and stdout to control behavior. Each event adds its own fields to the common structure below.

Input

All events include the following fields on stdin:
FieldDescription
session_idThe ID of the current session.
cwdThe current working directory.
hook_event_nameThe name of the event that triggered the hook.
Parse the input with jq:
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')

Output

A hook controls behavior through its exit code: 0 for success, 2 to block the operation (supported events only), and any other non-zero value for a non-blocking error. Some events also parse a JSON object on stdout for fine-grained control when the exit code is 0.

Environment variables

Hook scripts can access the following environment variables:
VariableDescription
QODERCN_PROJECT_DIRThe working directory of the current project.

Supported events

Qoder CN CLI supports the following hook events across the session lifecycle.

SessionStart

Triggers when a session starts. Matcher field: Session source
Matcher valueTrigger scenario
startupA new session starts.
resumeAn existing session is resumed.
compactContext compaction is complete.
Additional input fields:
{
  "source": "startup",
  "model": "Auto"
}

SessionEnd

Triggers when a session ends. Matcher field: End reason
Matcher valueTrigger scenario
prompt_input_exitThe user closes the prompt (e.g., by pressing Ctrl+D).
otherThe session ends for other reasons.
Additional input fields:
{
  "reason": "prompt_input_exit"
}

UserPromptSubmit

Triggers after a user submits a prompt but before the Agent processes it. Additional input fields:
{
  "prompt": "Write a sorting function for me"
}

PreToolUse

Triggers before a tool runs. This hook can block tool execution. matcher: Tool name (such as Bash, Write, Edit, Read, Glob, Grep, or an MCP tool name such as mcp__server__tool) Additional input fields:
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf /tmp/build"},
  "tool_use_id": "toolu_01ABC123"
}
Block tool execution: Exit with code 2. Content on stderr is sent to the Agent as an error message.

PostToolUse

Triggers 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

Triggers after a tool fails to execute. 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

Triggers when the Agent finishes its response with no pending tool calls. Use this hook to prevent the Agent from stopping and prompt it to continue. Prevent the Agent from stopping: Exit with code 2. Content from stderr is injected into the conversation, prompting the Agent to continue.

SubagentStart / SubagentStop

Triggers when a subagent starts or stops. SubagentStop works like Stop and can prevent a subagent from stopping. Matcher field: Agent type name Additional input fields:
{
  "agent_id": "a1b2c3d4",
  "agent_type": "task"
}

PreCompact

Triggers before context compaction. Matcher field: Trigger method
Matcher valueTrigger scenario
manualThe user manually executes /compact.
autoCompaction is triggered automatically when the context window is full.
Additional input fields:
{
  "trigger": "manual",
  "custom_instructions": "Keep all tool call results"
}

Notification

Triggers on notification events such as permission requests or task completions. Matcher field: Notification type
Matcher valueTrigger scenario
permissionA permission request notification.
resultA notification for a result generated by the Agent.
Additional input fields:
{
  "message": "Agent is requesting permission to run: rm -rf node_modules",
  "title": "Permission Required",
  "notification_type": "permission"
}

PermissionRequest

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

Use cases

Send desktop notifications

Send a macOS desktop notification when the Agent completes a task or requests authorization. Save the following script to ~/.qoder-cn/hooks/notify.sh:
#!/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 CN CLI"'
else
  osascript -e 'display notification "Authorization required" with title "Qoder CN CLI"'
fi
exit 0
Configuration:
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.qoder-cn/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}

Automatically lint modified files

Auto-lint JavaScript and TypeScript files after the Agent writes or edits them. Save the following script to ${project}/.qoder-cn/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
Set event to PostToolUse, matcher to Write|Edit, and command to .qoder/hooks/auto-lint.sh.

Keep the Agent running

Check for uncommitted Git changes when the Agent stops, and prompt it to continue if any exist. Save the following script to ~/.qoder-cn/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 commit your changes." >&2
  exit 2
fi
exit 0
Set event to Stop and command to ~/.qoder-cn/hooks/check-continue.sh.
Hooks - Qoder CN