All Products
Search
Document Center

AI Coding Assistant Lingma:Hooks

Last Updated:May 19, 2026

Hooks allow you to intercept the Agent's main execution flow at key points in Qoder CN 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 ~/.qodercn/hooks
cat > ~/.qodercn/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 ~/.qodercn/hooks/block-rm.sh

Step 2: Edit the configuration file

Add the following to ~/.qodercn/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.qodercn/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

Step 3: Verify

Start Qoder CN 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:

~/.qodercn/settings.json                    # User-level, applies to all projects
${project}/.qoderccn/settings.json           # Project-level, applies to the current project; can be committed to git for team sharing
${project}/.qodercn/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:

FieldRequiredDescriptiontypeYesFixed value: "command"commandYesThe shell command to executetimeoutNoTimeout 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).

SyntaxMeaningExampleOmitted or "*"Match allAll tools triggerExact valueExact match"Bash" matches only the Bash tool| separatedMatch multiple values"Write|Edit" matches Write or EditRegular 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:

FieldDescriptionsession_idCurrent session IDcwdCurrent working directoryhook_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:

VariableDescriptionQODERCN_PROJECT_DIRWorking directory of the current project

Supported Events

Qoder CN 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 scenariostartupNew session startedresumeExisting session resumedcompactAfter context compaction completes

Additional input fields:

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

SessionEnd

Triggered when a session ends.

matcher field: End reason

matcher valueTrigger scenarioprompt_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 scenariomanualUser manually runs /compactautoAutomatically 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 scenariopermissionPermission request notificationresultAgent 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 ~/.qodercn/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 CN CLI"'
else
  osascript -e 'display notification "Task requires authorization" with title "Qoder CN CLI"'
fi

exit 0

Configuration:

{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.qodercn/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}

Auto-Lint After Writing Files

Automatically run lint checks every time the Agent writes or edits a file.

Script ${project}/.qodercn/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 .qodercn/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 ~/.qodercn/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 ~/.qodercn/hooks/check-continue.sh.