Hooks 让你可以在 QoderWork 执行的关键节点插入自定义逻辑,而无需修改任何代码。你只需编辑 JSON 配置文件,就能实现诸如:
- 在工具执行前拦截危险操作
- 写文件后自动跑 lint,保持代码风格一致
- Agent 完成任务时弹出桌面通知,不用一直盯着屏幕
与 Prompt 指令不同,Hooks 是确定性的——只要事件触发,脚本就一定执行,不受模型理解偏差的影响。
快速开始
下面用一个例子,演示如何拦截 rm -rf 这类危险命令。
创建脚本
mkdir -p ~/.qoderwork/hooks
cat > ~/.qoderwork/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 "危险命令已被阻止: $command" >&2
exit 2
fi
exit 0
EOF
chmod +x ~/.qoderwork/hooks/block-rm.sh
添加配置
在 ~/.qoderwork/settings.json 中添加:{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/block-rm.sh"
}
]
}
]
}
}
验证效果
打开 QoderWork,让 Agent 执行包含 rm -rf 的命令。Hook 会阻止执行,并把错误信息反馈给 Agent。
工作原理
Hook 的使用流程可以概括为三步:编写脚本 → 注册配置 → 自动生效。
当 Agent 执行到某个生命周期节点时(如工具调用前),QoderWork 会检查配置中是否有对应的 Hook:
- QoderWork 在启动时加载所有 Hook 配置
- Agent 运行过程中,到达某个事件节点(如
PreToolUse)
- QoderWork 遍历该事件下所有 Hook 分组,用
matcher 匹配当前上下文
- 匹配成功的 Hook 按顺序执行对应的 shell 脚本
- 脚本通过
stdin 接收事件上下文(JSON),通过 exit code 和 stdout 返回决策
- QoderWork 根据返回结果决定后续行为(放行 / 阻断)
前置条件
- jq:示例脚本依赖 jq 解析 JSON。macOS 下执行
brew install jq,Linux 下执行 apt install jq。
- 脚本权限:所有 Hook 脚本需要有可执行权限(
chmod +x)。
创建 Hooks
1. 确定需求:选择事件和匹配范围
首先明确你要在哪个节点介入,以及匹配什么条件:
我想在「什么时候」拦截/处理「什么操作」?
↓ ↓
选择事件 编写 matcher
| 需求 | 事件 | matcher |
|---|
| Agent 执行任何 Shell 命令前检查 | PreToolUse | "Bash" |
| Agent 写入或编辑文件后做处理 | PostToolUse | "Write | Edit" |
| Agent 工具调用失败时记录 | PostToolUseFailure | "Bash" 或不填 |
| 用户提交的所有 Prompt 做审查 | UserPromptSubmit | 不填(匹配所有) |
| Agent 停止时触发通知 | Stop | 不填(匹配所有) |
| 只拦截 MCP 工具 | PreToolUse | "mcp__.*" |
2. 编写 Hook 脚本
Hook 脚本是一个标准的 shell 脚本,遵循以下协议:
输入:通过 stdin 接收 JSON 格式的事件上下文
输出:通过 exit code 控制行为
exit 0 → 放行(继续执行)
exit 2 → 阻断(停止操作,stderr 注入对话)
其他值 → 错误(继续执行,stderr 展示给用户)
脚本模板:
#!/bin/bash
# 1. 读取 stdin 的 JSON 输入
input=$(cat)
# 2. 用 jq 提取你关心的字段
tool_name=$(echo "$input" | jq -r '.tool_name')
tool_input=$(echo "$input" | jq -r '.tool_input')
# 3. 编写你的判断逻辑
if [ "$tool_name" = "Bash" ]; then
command=$(echo "$input" | jq -r '.tool_input.command')
if echo "$command" | grep -qE 'rm\s+-rf|DROP\s+TABLE'; then
echo "操作被拒绝: $command" >&2
exit 2
fi
fi
# 4. 放行
exit 0
除了 exit code,你还可以在 exit 0 时输出 JSON 来提供更精细的控制:#!/bin/bash
input=$(cat)
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"该操作不被允许"}}'
exit 0
3. 注册到配置文件
将脚本路径写入 ~/.qoderwork/settings.json 的对应事件下:
{
"hooks": {
"事件名": [
{
"matcher": "匹配条件(可选)",
"hooks": [
{
"type": "command",
"command": "脚本路径"
}
]
}
]
}
}
4. 测试与调试
可以直接在终端用管道模拟测试:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"},"hook_event_name":"PreToolUse"}' \
| ~/.qoderwork/hooks/block-rm.sh
echo "Exit code: $?"
检查 stderr 输出(阻断消息):
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
| ~/.qoderwork/hooks/block-rm.sh 2>&1
配置 Hooks
配置文件位置
QoderWork 从以下用户级配置文件加载 Hook 配置:
| 位置 | 作用域 | 说明 |
|---|
~/.qoderwork/settings.json | 用户级(全局) | 用户个人配置,对所有 QoderWork 会话生效 |
当前版本暂不支持热加载,修改配置文件后需要重启 QoderWork 才能生效。
配置格式
{
"hooks": {
"事件名": [
{
"matcher": "匹配条件",
"hooks": [
{
"type": "command",
"command": "要执行的命令"
}
]
}
]
}
}
| 字段 | 必填 | 说明 |
|---|
type | 是 | 固定为 "command" |
command | 是 | 要执行的 shell 命令或脚本路径 |
timeout | 否 | 超时时间(秒),默认 30。当前版本不支持自定义,下个版本开放配置 |
matcher | 否 | 匹配条件,不填则匹配该事件的所有触发 |
一个事件下可以配置多个 matcher 分组,每个分组可以包含多个 Hook 命令。
matcher 匹配规则
matcher 用于过滤 Hook 的触发范围,不同事件匹配不同的字段(见各事件说明)。
| 写法 | 含义 | 示例 |
|---|
不填或 "*" | 匹配所有 | 所有工具都会触发 |
| 精确值 | 精确匹配 | "Bash" 只在 Bash 工具时触发 |
| 分隔 | 匹配多个值 | "Write | Edit" 在 Write 或 Edit 时触发 |
| 正则表达式 | 正则匹配 | "mcp__.*" 匹配所有 MCP 工具 |
工具名映射
QoderWork 支持两套工具名——原生工具名与兼容工具名。配置 Hook 时可使用任意一套,QoderWork 内部会统一映射后执行匹配。
| 原生名 | 兼容名 | 说明 |
|---|
run_in_terminal | Bash | 执行 shell 命令 |
read_file | Read | 读取文件内容 |
create_file | Write | 创建 / 写入文件 |
search_replace | Edit | 编辑文件 |
delete_file | - | 删除文件 |
grep_code | Grep | 搜索文件内容 |
search_file | Glob | 文件名匹配 |
list_dir | LS | 列出目录 |
task | Task | 启动子任务 / 子代理 |
Skill | - | 调用技能 |
search_web | WebSearch | 网页搜索 |
fetch_content | WebFetch | 获取网页内容 |
todo_write | TodoWrite | 写入 TODO |
ask_user_question | - | 向用户提问 |
search_memory | - | 搜索记忆 |
update_memory | - | 更新记忆 |
mcp__<server>__<tool> | 同左 | MCP 工具 |
编写 Hook 脚本
Hook 脚本通过 stdin 接收 JSON 输入,通过 exit code 和 stdout 输出来控制行为。本节说明所有事件通用的输入输出格式,各事件的额外字段见 Hooks 事件。
QoderWork 目前不会向 Hook 脚本注入环境变量。所有数据通过 stdin JSON 传递。如需会话 ID、工作目录或工具信息,请从 stdin JSON 输入中解析。
Hook 脚本通过 stdin 接收 JSON 数据。所有事件都包含以下通用字段:
| 字段 | 说明 |
|---|
session_id | 当前会话 ID |
transcript_path | 会话 Transcript JSONL 文件路径(如 ~/.qoderwork/projects/<编码路径>/<session-id>.jsonl) |
cwd | 当前工作目录 |
hook_event_name | 触发的事件名称 |
不同事件会在此基础上附加额外字段(见各 Hooks 事件)。
用 jq 解析输入:
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
Hook 通过 exit code 和 stdout 控制行为。
exit 0 表示成功。QoderWork 会解析 stdout,部分事件(如 UserPromptSubmit、SessionStart)支持纯文本上下文注入或 JSON 精细控制,具体见各事件说明。
exit 2 表示阻塞错误,仅对支持阻塞的事件生效。stdout 被忽略,stderr 作为错误信息反馈给 Agent。具体效果取决于事件:PreToolUse 阻止工具执行,UserPromptSubmit 拒绝 prompt,Stop 阻止 Agent 停止,等等。
其他 exit code 视为非阻塞错误,不影响执行流程,stderr 仅记录在日志中。
Hooks 事件
UserPromptSubmit
用户提交 Prompt 后、Agent 开始处理前触发。可用于 Prompt 审查、内容过滤、自动补充上下文等场景。
matcher 匹配: 无匹配字段,该事件对所有用户输入统一触发。
额外输入字段:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "UserPromptSubmit",
"prompt": "帮我整理下载文件夹"
}
阻止 Prompt 提交: exit code 2,stderr 内容作为错误信息反馈给用户,Agent 不会处理该 Prompt。
stdout JSON 输出字段(exit 0 时):
| 字段 | 类型 | 说明 |
|---|
hookSpecificOutput.hookEventName | string | 固定为 "UserPromptSubmit" |
hookSpecificOutput.additionalContext | string | 附加上下文信息,会注入到 Agent 的对话中 |
工具执行前触发。可以阻止工具执行。 适用于危险命令拦截、文件路径校验、权限检查等场景。
matcher 匹配: 工具名(如 Bash、Write、Edit、Read,MCP 工具名如 mcp__server__tool)
额外输入字段:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "rm -rf /tmp/build" }
}
阻止工具执行: exit code 2,stderr 内容作为错误返回给 Agent。
stdout JSON 输出字段(exit 0 时):
| 字段 | 类型 | 说明 |
|---|
hookSpecificOutput.hookEventName | string | 固定为 "PreToolUse" |
hookSpecificOutput.permissionDecision | string | "allow"(放行)、"deny"(拒绝)或 "ask"(询问用户) |
hookSpecificOutput.permissionDecisionReason | string | 决策原因 |
hookSpecificOutput.updatedInput | object | 修改后的工具输入参数(可选) |
hookSpecificOutput.additionalContext | string | 附加上下文信息(可选) |
PostToolUse
工具执行成功后触发。不可阻断。适用于自动 lint、日志记录、结果分析等场景。
matcher 匹配: 工具名
额外输入字段:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": { "file_path": "/path/to/file.ts", "content": "..." },
"tool_response": "File written successfully"
}
stdout JSON 输出字段(exit 0 时):
| 字段 | 类型 | 说明 |
|---|
hookSpecificOutput.hookEventName | string | 固定为 "PostToolUse" |
hookSpecificOutput.feedback | string | 反馈信息,会展示给用户 |
PostToolUseFailure
工具调用失败后触发。不可阻断。适用于错误监控、失败重试提示、日志记录等场景。
matcher 匹配: 工具名
额外输入字段:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "PostToolUseFailure",
"tool_name": "Bash",
"tool_input": { "command": "npm test" },
"error": "Command exited with non-zero status code 1"
}
| 字段 | 类型 | 说明 |
|---|
error | string | 工具执行的错误信息 |
Stop
Agent 完成响应后触发。可以阻止 Agent 停止。 适用于质量门禁、桌面通知、日志记录等场景。
matcher 匹配: 无匹配字段,该事件在 Agent 停止时统一触发。
额外输入字段:
{
"session_id": "abc-123",
"cwd": "/Users/you",
"hook_event_name": "Stop",
"stop_hook_active": false
}
| 字段 | 类型 | 说明 |
|---|
stop_hook_active | boolean | 当 Agent 因 Stop Hook 阻断后重试时为 true。脚本必须检查此字段,在 true 时 exit 0,防止无限循环。 |
阻止 Agent 停止: exit code 2,阻止原因注入对话,Agent 继续工作。
stdout JSON 输出字段(exit 0 时):
{
"decision": "block",
"reason": "Tests failing. Fix them before completing."
}
| 字段 | 类型 | 说明 |
|---|
decision | string | "block"(阻止停止,让 Agent 继续工作) |
reason | string | 阻止的原因,会作为消息注入对话 |
防止无限循环: 当 Stop Hook 阻断 Agent(exit 2)后,Agent 重试时 Stop 事件会再次触发,此时 stop_hook_active: true。脚本必须检查此字段并在 true 时 exit 0,否则 Hook 会无限阻断。
SessionStart
会话开始时触发。
matcher 匹配: 会话来源
| matcher 值 | 触发场景 |
|---|
startup | 新会话启动 |
resume | 恢复已有会话 |
compact | 上下文压缩完成后 |
额外输入字段:
{
"source": "startup",
"agent_type": "coder",
"model": ""
}
SessionEnd
会话结束时触发。
matcher 匹配: 结束原因
| matcher 值 | 触发场景 |
|---|
prompt_input_exit | 用户退出输入(Ctrl+D 等) |
other | 其他原因 |
额外输入字段:
{
"reason": "prompt_input_exit"
}
SubagentStart / SubagentStop
子 Agent 启动和完成时触发。SubagentStop 与 Stop 类似,可以阻止子 Agent 停止。
matcher 匹配: Agent 类型名
SubagentStart 额外输入字段:
{
"agent_id": "83f488ef",
"agent_type": "general-purpose"
}
SubagentStop 额外输入字段:
{
"agent_id": "83f488ef",
"agent_type": "general-purpose",
"agent_transcript_path": "",
"stop_hook_active": false
}
| 字段 | 类型 | 说明 |
|---|
agent_id | string | 子 Agent ID |
agent_type | string | 子 Agent 类型(如 "general-purpose") |
agent_transcript_path | string | 子 Agent 的 Transcript 文件路径(仅 SubagentStop) |
stop_hook_active | boolean | 是否为上次 SubagentStop 阻断后的重试(仅 SubagentStop) |
PreCompact
上下文压缩前触发。
matcher 匹配: 触发方式
| matcher 值 | 触发场景 |
|---|
manual | 用户手动执行 /compact |
auto | 上下文窗口满时自动触发 |
额外输入字段:
{
"trigger": "manual",
"custom_instructions": "保留所有工具调用结果"
}
Notification
通知事件触发(权限请求、任务完成等)。
matcher 匹配: 通知类型
| matcher 值 | 触发场景 |
|---|
permission | 权限请求通知 |
result | Agent 产出结果通知 |
额外输入字段:
{
"message": "Agent is requesting permission to run: rm -rf node_modules",
"title": "Permission Required",
"notification_type": "permission"
}
PermissionRequest
工具执行需要用户授权时触发。
matcher 匹配: 工具名
额外输入字段:
{
"tool_name": "Bash",
"tool_input": {"command": "rm -rf node_modules"}
}
场景示例
拦截危险命令
脚本 ~/.qoderwork/hooks/block-dangerous.sh:
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')
if echo "$command" | grep -qE 'rm\s+-rf|DROP\s+TABLE|mkfs|dd\s+if='; then
echo "危险命令已被阻止: $command" >&2
exit 2
fi
exit 0
配置:事件 PreToolUse,matcher Bash,command ~/.qoderwork/hooks/block-dangerous.sh。
写文件后自动 Lint
每次 Agent 写入或编辑文件后,自动执行 lint 检查。
脚本 ~/.qoderwork/hooks/auto-lint.sh:
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
# 只检查 JS/TS 文件
case "$file_path" in
*.js|*.ts|*.jsx|*.tsx)
npx eslint "$file_path" --fix 2>/dev/null
;;
esac
exit 0
配置:事件 PostToolUse,matcher Write|Edit,command ~/.qoderwork/hooks/auto-lint.sh。
桌面通知提醒
当 Agent 完成任务或需要授权时,弹出桌面通知。
脚本 ~/.qoderwork/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 "任务执行完成" with title "QoderWork"'
else
osascript -e 'display notification "任务需要授权" with title "QoderWork"'
fi
exit 0
配置:事件 Notification,无 matcher,command ~/.qoderwork/hooks/notify.sh。
Prompt 内容审查
脚本 ~/.qoderwork/hooks/check-prompt.sh:
#!/bin/bash
input=$(cat)
prompt=$(echo "$input" | jq -r '.prompt')
if echo "$prompt" | grep -qiE '(password|secret|api_key|token)\s*[:=]\s*\S+'; then
echo "检测到 Prompt 中可能包含敏感信息,请检查后重新提交" >&2
exit 2
fi
exit 0
配置:事件 UserPromptSubmit,无 matcher,command ~/.qoderwork/hooks/check-prompt.sh。
工具失败时记录日志
脚本 ~/.qoderwork/hooks/log-failure.sh:
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
error=$(echo "$input" | jq -r '.error')
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $tool_name 执行失败: $error" >> ~/.qoderwork/hooks/failure.log
exit 0
配置:事件 PostToolUseFailure,无 matcher,command ~/.qoderwork/hooks/log-failure.sh。
让 Agent 继续工作
在 Agent 停止时检查是否还有未完成的任务,如果有则注入消息让 Agent 继续。
脚本 ~/.qoderwork/hooks/check-continue.sh:
#!/bin/bash
# 检查是否有未提交的 git 变更
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
echo "检测到未提交的变更,请完成 git commit" >&2
exit 2
fi
exit 0
配置:事件 Stop,command ~/.qoderwork/hooks/check-continue.sh。
提交 Prompt 时注入上下文
在每次用户提问前,自动注入当前 git 分支信息作为 Agent 上下文。
脚本 ~/.qoderwork/hooks/inject-branch.sh:
#!/bin/bash
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ -n "$branch" ]; then
echo "当前 git 分支: $branch"
fi
exit 0
配置:事件 UserPromptSubmit,command ~/.qoderwork/hooks/inject-branch.sh。
阻止包含敏感信息的 Prompt
拦截包含密码、密钥等敏感关键词的 prompt,阻止其发送给 Agent。
脚本 ~/.qoderwork/hooks/block-sensitive.sh:
#!/bin/bash
input=$(cat)
prompt=$(echo "$input" | jq -r '.prompt')
if echo "$prompt" | grep -qi 'password\|secret\|credential'; then
echo "prompt 中包含敏感信息,已阻止" >&2
exit 2
fi
exit 0
配置:事件 UserPromptSubmit,command ~/.qoderwork/hooks/block-sensitive.sh。
完整配置示例
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/check-prompt.sh"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/block-dangerous.sh"
}
]
}
],
"PostToolUseFailure": [
{
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/log-failure.sh"
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "~/.qoderwork/hooks/notify.sh"
}
]
}
]
}
}
注意事项
- 超时处理: Hook 脚本默认超时 30 秒。超时后脚本将被终止,按放行处理。自定义超时时间将在下个版本中支持。
- 错误处理: 脚本异常退出(exit code 非 0 且非 2)时,错误信息显示给用户,Agent 流程不受影响继续执行。
- 脚本权限: 确保脚本具有可执行权限(
chmod +x)。
- jq 依赖: 示例脚本依赖
jq 命令行工具来解析 JSON。请确保系统已安装 jq(macOS: brew install jq,Linux: apt install jq)。
- 重启生效: 修改
~/.qoderwork/settings.json 后,需要重启 QoderWork 才能生效。