All Products
Search
Document Center

Qoder CN Series:Hooks

Last Updated:Jun 03, 2026

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:

Field

Required

Description

type

Yes

Must be set to "command".

command

Yes

The shell command to run.

timeout

No

The timeout in seconds. Default: 60.

matcher

No

Match 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 "*"

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__.*" 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:

Field

Description

session_id

The ID of the current session.

cwd

The current working directory.

hook_event_name

The 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:

Variable

Description

QODERCN_PROJECT_DIR

The 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 value

Trigger scenario

startup

A new session starts.

resume

An existing session is resumed.

compact

Context compaction is complete.

Additional input fields:

{
  "source": "startup",
  "model": "Auto"
}

SessionEnd

Triggers when a session ends.

Matcher field: End reason

Matcher value

Trigger scenario

prompt_input_exit

The user closes the prompt (e.g., by pressing Ctrl+D).

other

The 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 value

Trigger scenario

manual

The user manually executes /compact.

auto

Compaction 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 value

Trigger scenario

permission

A permission request notification.

result

A 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-cn/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.