Hooks
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 |
|
|
After a user submits a prompt, but before the Agent processes it. |
Yes |
|
|
Before a tool call is executed. |
Yes |
|
|
After a tool call succeeds. |
No |
|
|
After a tool call fails. |
No |
|
|
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 |
|
Block commands like |
|
Validate file paths |
|
Restrict file writes to designated directories. |
|
Auto-lint / format |
|
Automatically run ESLint or Prettier after file writes. |
|
Audit logging |
|
Log all Agent tool calls for security auditing. |
|
Monitor for failures |
|
Send alerts or log errors when a tool call fails. |
|
Screen prompt content |
|
Scan prompts for sensitive data like passwords or keys. |
|
Auto-inject context |
|
Append project guidelines or coding conventions to prompts. |
|
Desktop notifications |
|
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:
-
The IDE loads all hook configurations on startup.
-
The Agent reaches an event node (e.g., PreToolUse).
-
The extension iterates Hook groups, matching against the current context.
-
Matched scripts execute in declared order.
-
The script receives event context (JSON) via stdin and returns a decision through exit code and stdout.
-
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) orapt 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 |
|
|
Process files after writes or edits. |
PostToolUse |
|
|
Log when an Agent tool call fails. |
PostToolUseFailure |
|
|
Screen all user prompts. |
UserPromptSubmit |
Omit (matches all) |
|
Trigger a notification when the Agent stops. |
Stop |
Omit (matches all) |
|
Intercept MCP tools only. |
PreToolUse |
|
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 |
|
|
User |
1 (Lowest) |
No |
Applies to all projects. |
|
|
Project |
2 |
Yes |
Committable to Git for team sharing. |
|
|
Project (Local) |
3 (Highest) |
No |
Personal overrides. Add to |
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 |
|
|
Yes |
Must be set to |
|
|
Yes |
The shell command or script path to execute. |
|
|
No |
Timeout in seconds. Default: 30. |
|
|
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 |
|
|
|
Match multiple values |
|
|
Regular expression |
Regex match |
|
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 |
|
|
The current session ID. |
|
|
The current working directory. |
|
|
The name of the triggered event. |
|
|
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.