Hooks

Updated at:
Important

The new Qoder CN documentation site is now available. Content on this site may not reflect the latest product features and changes. Visit docs.qoder.cn for the latest product documentation.

Hooks inject custom logic at key points in the Qoder CN Agent's lifecycle. Through a JSON configuration file, you can:

  • Blocking dangerous operations before a tool call.

  • Running a linter after every file write to enforce code style.

  • Receiving a desktop notification when the Agent completes a task.

Unlike prompt instructions, Hooks are deterministic — when a registered event occurs, the script executes regardless of model interpretation.

Supported events

Qoder CN supports five hook events:

Event name

Trigger

Blockable

UserPromptSubmit

After a user submits a prompt, but before the Agent processes it.

Yes

PreToolUse

Before a tool call is executed.

Yes

PostToolUse

After a tool call succeeds.

No

PostToolUseFailure

After a tool call fails.

No

Stop

When the Agent completes its response.

No

Quick start

This example configures a hook that blocks rm -rf.

Step 1: Create the script

mkdir -p ~/.lingma/hooks
cat > ~/.lingma/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 ~/.lingma/hooks/block-rm.sh

Step 2: Add the configuration

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

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

Step 3: Verify the result

Restart the IDE. In the Qoder CN panel, ask the Agent to run a command containing rm -rf. The hook blocks execution and feeds the error message back to the Agent.

Use cases

Scenario

Event

Description

Block dangerous commands

PreToolUse

Block commands like rm -rf or DROP TABLE.

Validate file paths

PreToolUse

Restrict file writes to designated directories.

Auto-lint / format

PostToolUse

Automatically run ESLint or Prettier after file writes.

Audit logging

PostToolUse

Log all Agent tool calls for security auditing.

Monitor for failures

PostToolUseFailure

Send alerts or log errors when a tool call fails.

Screen prompt content

UserPromptSubmit

Scan prompts for sensitive data like passwords or keys.

Auto-inject context

UserPromptSubmit

Append project guidelines or coding conventions to prompts.

Desktop notifications

Stop

Send a desktop notification when the Agent finishes.

How it works

Hooks require two steps: write a script and register it in the configuration file.

When the Agent reaches a lifecycle event, the extension checks for matching hooks:

  1. The IDE loads all hook configurations on startup.

  2. The Agent reaches an event node (e.g., PreToolUse).

  3. The extension iterates Hook groups, matching against the current context.

  4. Matched scripts execute in declared order.

  5. The script receives event context (JSON) via stdin and returns a decision through exit code and stdout.

  6. The IDE uses the result to allow or block the operation.

Prerequisites

  • jq: Used to parse JSON in the examples. Install with brew install jq (macOS) or apt install jq (Linux).

  • Script permissions: Hook scripts must be executable (chmod +x).

Creating hooks

Step 1: Define the requirement

Determine when to intervene and what to match:

I want to intercept/handle "what operation" "when"?
                              ↓             ↓
                           Write matcher   Choose event

Requirement

Event

Matcher

Inspect shell commands before execution.

PreToolUse

"Bash"

Process files after writes or edits.

PostToolUse

"Write | Edit"

Log when an Agent tool call fails.

PostToolUseFailure

"Bash" or omit

Screen all user prompts.

UserPromptSubmit

Omit (matches all)

Trigger a notification when the Agent stops.

Stop

Omit (matches all)

Intercept MCP tools only.

PreToolUse

"mcp__.*"

Step 2: Write the hook script

Hook scripts follow this protocol:

  • Input: Receives event context as a JSON object via stdin.

  • Output: Controls behavior through its exit code.

exit 0   →  Allow (proceed with execution)
exit 2   →  Block (stop the operation; stderr is fed back to the Agent)
Other    →  Error (proceed with execution; stderr is displayed to the user)

Script template:

#!/bin/bash

# 1. Read JSON input from stdin.
input=$(cat)

# 2. Extract the fields you need with jq.
#    Different events have different fields; see the "Event Reference" section for details.
tool_name=$(echo "$input" | jq -r '.tool_name')
tool_input=$(echo "$input" | jq -r '.tool_input')

# 3. Write your validation logic.
if [ "$tool_name" = "Bash" ]; then
  command=$(echo "$input" | jq -r '.tool_input.command')

  # Check for dangerous operations.
  if echo "$command" | grep -qE 'rm\s+-rf|DROP\s+TABLE'; then
    # Block: exit 2 + stderr message is fed back to the Agent.
    echo "Operation denied: $command" >&2
    exit 2
  fi
fi

# 4. Allow the operation.
exit 0

You can also output JSON on exit 0 for finer control:

#!/bin/bash
input=$(cat)

# Output JSON for fine-grained control (only takes effect on exit 0).
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"This operation is not allowed"}}'
exit 0

Step 3: Register the script in the configuration file

Register the script under the appropriate event:

{
  "hooks": {
    "EventName": [
      {
        "matcher": "MatchCondition (optional)",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/your/script.sh"
          }
        ]
      }
    ]
  }
}

Step 4: Test and debug

Test hooks by piping JSON input from the terminal:

# Simulate a PreToolUse event
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"},"hook_event_name":"PreToolUse"}' \
  | ~/.lingma/hooks/block-rm.sh
echo "Exit code: $?"

Check the stderr output (block message):

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
  | ~/.lingma/hooks/block-rm.sh 2>&1

Configuring hooks

Configuration file locations

Hooks merge from these files, lowest to highest priority:

Location

Scope

Priority

Shareable

Description

~/.lingma/settings.json

User

1 (Lowest)

No

Applies to all projects.

.lingma/settings.json

Project

2

Yes

Committable to Git for team sharing.

.lingma/settings.local.json

Project (Local)

3 (Highest)

No

Personal overrides. Add to .gitignore.

Note

Hot reloading is not supported. Restart the IDE for configuration changes to take effect.

Configuration format

{
  "hooks": {
    "EventName": [
      {
        "matcher": "MatchCondition",
        "hooks": [
          {
            "type": "command",
            "command": "command_to_run"
          }
        ]
      }
    ]
  }
}

Field

Required

Description

type

Yes

Must be set to "command".

command

Yes

The shell command or script path to execute.

timeout

No

Timeout in seconds. Default: 30.

matcher

No

Filters when the hook runs. If omitted, triggers for all event invocations.

A single event can have multiple matcher groups, each with multiple hook commands.

Matcher rules

A matcher narrows when a hook fires. Matched fields vary by event.

Syntax

Meaning

Example

Omit or "*"

Match all

Triggers for all tools.

Exact value

Exact match

"Bash" triggers only for the Bash tool.

| separated

Match multiple values

"Write | Edit" triggers for Write or Edit tools.

Regular expression

Regex match

"mcp__.*" matches all MCP tools.

Tool name mapping

Qoder CN maps two sets of tool names — native and compatible — at runtime. For example, matcher: "Bash" and matcher: "run_in_terminal" are equivalent.

Native name

Compatible name

Description

run_in_terminal

Bash

Execute a shell command.

read_file

Read

Read the content of a file.

create_file

Write

Create or write to a file.

search_replace

Edit

Edit a file.

delete_file

-

Delete a file.

grep_code

Grep

Search file content.

search_file

Glob

Match file names.

list_dir

LS

List a directory's contents.

task

Task

Start a subtask or sub-agent.

mcp__<server>__<tool>

Same as native

An MCP tool.

Writing hook scripts

Hook scripts receive JSON via stdin and signal decisions through exit code and stdout. Event-specific fields are covered in the Event reference.

Input

All events share these stdin fields:

Field

Description

session_id

The current session ID.

cwd

The current working directory.

hook_event_name

The name of the triggered event.

transcript_path

The file path to the session context JSON.

Events add fields beyond this base (listed in the Event reference). Parse with jq:

#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')

Output

A script signals its decision via its exit code and any data written to stdout.

The exit code determines the basic behavior:

Exit code

Meaning

Behavior

0

Success

Continues execution; parses stdout JSON.

2

Block

Blocks the operation and feeds stderr to the Agent (blockable events only).

Other

Error

Continues execution; stderr is shown to the user.

stdout JSON (optional; only takes effect when exit code is 0):

{
  "continue": true,
  "stopReason": "",
  "suppressOutput": false,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow | deny | ask",
    "permissionDecisionReason": "Reason for the decision"
  }
}

Event reference

UserPromptSubmit

  • Trigger: After the user submits a prompt, but before the Agent processes it.

  • Blockable: Yes (exit code 2 cancels the request).

  • Additional input fields: prompt (the raw text entered by the user).

PreToolUse

  • Trigger: Before the Agent executes a tool call.

  • Blockable: Yes (exit code 2 rejects the tool call).

  • Matcher field: tool_name.

  • Additional input fields: tool_name, tool_input.

PostToolUse

  • Trigger: After a tool call succeeds.

  • Blockable: No.

  • Matcher field: tool_name.

  • Additional input fields: tool_name, tool_input, tool_response.

PostToolUseFailure

  • Trigger: After a tool call fails.

  • Blockable: No.

  • Matcher field: tool_name.

  • Additional input fields: tool_name, tool_input, error.

Stop

  • Trigger: When the Agent completes its response.

  • Blockable: No.

  • Additional input fields: stop_hook_active, last_assistant_message.

FAQ

Why don't my configuration changes take effect?

Hot reloading is not supported. Restart the IDE after modifying settings.json.

What happens if a hook script times out?

A timeout (default: 30s) is treated as an error. Execution continues and stderr is shown to the user.

How is the execution order determined for multiple hooks?

Same-event hooks run in declared order. Across files, priority applies: user (lowest) → project → project-local (highest).

What environment variables can hook scripts access?

Hooks run in the project root as the current user, inheriting the IDE process environment.

How do I view hook execution logs?

stderr output goes to the Agent or user. For detailed logs, search Qoder CN runtime logs for the [hook] prefix.