Documentation Index
Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
Use this file to discover all available pages before exploring further.
Hooks allow you to intercept the Agent’s main execution flow at key points in Qoder 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 ~/.qoder/hooks
cat > ~/.qoder/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/hooks/block-rm.sh
Step 2: Edit the configuration file
Add the following to ~/.qoder/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.qoder/hooks/block-rm.sh"
}
]
}
]
}
}
Step 3: Verify
Start Qoder 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:
~/.qoder/settings.json # User-level, applies to all projects
${project}/.qoder/settings.json # Project-level, applies to the current project; can be committed to git for team sharing
${project}/.qoder/settings.local.json # Project-level (local), highest priority; recommended to add to .gitignore
{
"hooks": {
"EventName": [
{
"matcher": "match condition",
"hooks": [
{
"type": "command",
"command": "command to execute",
"timeout": 60
}
]
}
]
}
}
Field descriptions:
| Field | Required | Description |
|---|
type | Yes | Fixed value: "command" |
command | Yes | The shell command to execute |
timeout | No | Timeout in seconds (default: 60) |
matcher | No | Match 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).
| Syntax | Meaning | Example |
|---|
Omitted or "*" | Match all | All tools trigger |
| Exact value | Exact match | "Bash" matches only the Bash tool |
| separated | Match multiple values | "Write|Edit" matches Write or Edit |
| Regular expression | Regex 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.
Hook scripts receive JSON data via stdin. All events include the following common fields:
| Field | Description |
|---|
session_id | Current session ID |
cwd | Current working directory |
hook_event_name | Name 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:
| Variable | Description |
|---|
QODER_PROJECT_DIR | Working directory of the current project |
Supported Events
Qoder CLI supports the following Hook events, covering all stages of the session lifecycle.
SessionStart
Triggered when a session starts.
matcher field: Session source
| matcher value | Trigger scenario |
|---|
startup | New session started |
resume | Existing session resumed |
compact | After context compaction completes |
Additional input fields:
{
"source": "startup",
"model": "Auto"
}
SessionEnd
Triggered when a session ends.
matcher field: End reason
| matcher value | Trigger scenario |
|---|
prompt_input_exit | User exits input (Ctrl+D, etc.) |
other | Other 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"
}
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 value | Trigger scenario |
|---|
manual | User manually runs /compact |
auto | Automatically 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 value | Trigger scenario |
|---|
permission | Permission request notification |
result | Agent 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 ~/.qoder/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 CLI"'
else
osascript -e 'display notification "Task requires authorization" with title "Qoder CLI"'
fi
exit 0
Configuration:
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "~/.qoder/hooks/notify.sh"
}
]
}
]
}
}
Auto-Lint After Writing Files
Automatically run lint checks every time the Agent writes or edits a file.
Script ${project}/.qoder/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 .qoder/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 ~/.qoder/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 ~/.qoder/hooks/check-continue.sh.