跳转到主要内容
Hooks 允许在 Qoder CLI 的关键节点介入 Agent 的主执行流,同时与 CLI 保持解耦。常见用途包括:工具执行前拦截危险操作、任务完成后发送桌面通知、写文件后自动跑 lint 等。 Hooks 通过 JSON 配置文件定义,不需要修改代码,编辑配置文件即可生效。

快速开始

以下示例演示如何用 Hook 拦截危险命令——当 Agent 尝试执行 rm -rf 时自动阻止。 第一步:创建脚本
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 "危险命令已被阻止: $command" >&2
  exit 2
fi

exit 0
EOF
chmod +x ~/.qoder/hooks/block-rm.sh
第二步:编辑配置文件 ~/.qoder/settings.json 中添加:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.qoder/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
第三步:验证效果 启动 Qoder CLI,让 Agent 执行包含 rm -rf 的命令,Hook 会阻止执行并提示 Agent。

配置

配置文件位置

Hook 配置从以下三个文件加载,三个来源都会合并执行(同事件下的 hook 不互相覆盖):
~/.qoder/settings.json                    # 用户级,对所有项目生效
${project}/.qoder/settings.json           # 项目级,对当前项目生效,可提交 git 共享给团队
${project}/.qoder/settings.local.json     # 项目级(本地),建议加到 .gitignore

配置格式

{
  "hooks": {
    "事件名": [
      {
        "matcher": "匹配条件",
        "hooks": [
          {
            "type": "command",
            "command": "要执行的命令",
            "timeout": 600
          }
        ]
      }
    ]
  }
}
一个事件下可以配置多个 matcher 分组,每个分组可以包含多个 hook 条目。 分组(HookDefinition)字段
字段必填说明
matcher匹配条件,不填则匹配所有
hooks该分组下的 hook 条目数组
async设为 true 时,该分组下所有 hook 在后台执行,不阻塞当前操作;结果在下一轮模型对话中作为附加上下文注入

Hook 条目类型

每个 hook 条目通过 type 字段声明类型,不同类型有各自的字段。

command(执行 shell 命令)

{
  "type": "command",
  "command": "~/.qoder/hooks/check.sh",
  "timeout": 600,
  "shell": "bash",
  "env": { "FOO": "bar" }
}
字段必填说明
command要执行的 shell 命令
timeout超时时间(秒),默认 600
shell指定 shell,可选 "bash""powershell";不填则使用系统默认 shell
env额外环境变量,会与系统环境合并
if条件过滤,格式为 "ToolName""ToolName(arg_pattern)",仅在工具名/参数匹配时触发
async设为 true 时该 hook 单独后台执行,覆盖分组级 async
asyncRewake设为 true 时后台执行;若以 exit 2 退出,会用 stderr/stdout/error 内容生成一条系统提醒并唤醒模型继续处理,常用于长耗时检查
rewakeMessage配合 asyncRewake,覆盖注入消息的前缀
rewakeSummary配合 asyncRewake,覆盖一行摘要(最长 300 字符)
once设为 true 时该 hook 在首次成功执行后从注册表移除,仅对会话级 hook 生效
statusMessage自定义状态行/spinner 中显示的描述
args可选的 argv 数组。设置后该 hook 以 exec 形态运行(不经 shell)。详见下文Exec form vs Shell form 一节。
command 中引用占位符
${QODER_PROJECT_DIR}${QODER_PLUGIN_ROOT} 等占位符会作为环境变量注入到 hook 子进程(详见环境变量)。在 bash 下,CLI 不会预先替换命令模板,而是由 shell 在运行时展开。推荐的写法:
  • 双引号包裹占位符(推荐):"${QODER_PLUGIN_ROOT}"/scripts/hook.sh。路径中含空格或 shell 元字符('$、反引号等)也能作为单个 token 正确解析。
  • 普通 shell 变量语法"$QODER_PLUGIN_ROOT/scripts/hook.sh"。配合双引号时与上一种等价。
  • 不加引号(不推荐):${QODER_PLUGIN_ROOT}/scripts/hook.sh。POSIX shell 对未引用的参数展开仍会做单词拆分与路径名展开,路径含空格会被拆分、含 * 会被通配展开。
用于检测 ${QODER_PLUGIN_ROOT}${QODER_PLUGIN_DATA} 出现在非 plugin hook 中的 preflight 仅匹配 ${...} 形式(避免对字面量 $VAR 文本产生误报)。希望 preflight 兜底的 plugin 作者请使用 ${...} 形式。
powershell 下,${QODER_PROJECT_DIR}${QODER_PLUGIN_ROOT}${QODER_PLUGIN_DATA} 由 CLI 在调用前替换进命令模板(因为 PowerShell 用 $env:NAME 而不是 ${NAME} 访问环境变量)。
Exec form vs Shell form
命令 hook 支持两种执行形态:
  • Shell 形态(默认):command 是一段 shell 代码片段。CLI 调用 bash -c "<command>"(或 PowerShell)。支持管道、重定向、glob 展开和 ${VAR} 环境变量展开。
  • Exec 形态(当 args 设置时):command 是单个可执行文件的路径/名称,args 的每一项是一个字面的 argv 元素。CLI 直接调用二进制,不经过 shell——不做任何引号处理、单词拆分或 glob 展开。设置了 argsshell 字段被忽略。
{
  "type": "command",
  "command": "/usr/bin/python3",
  "args": ["${QODER_PLUGIN_ROOT}/scripts/check.py", "--strict"]
}
如何选择:
  • 默认用 shell 形态——只要把占位符包在双引号里("${QODER_PLUGIN_ROOT}"/scripts/check.sh),env 透出就能处理含空格、单引号、$、反引号等的路径。
  • 用 shell 形态当: hook 需要管道(grep | tee)、重定向(>)、glob(*.json)或其他 shell 特性。
  • 用 exec 形态当: 路径或参数中含 shell 元字符需要复杂引号;或者你想完全避免 shell 解析的不确定性。
Windows 下 exec 形态的注意事项:
  • .bat / .cmd 脚本无法直接 exec,要写成 {"command": "cmd.exe", "args": ["/c", "script.bat"]}
  • MSYS / Cygwin 程序的 argv 语义由其自身定义,参数中需要的引号要查所调用程序的文档。

http(发送 HTTP 请求)

将 hook 输入以 JSON POST 至指定 URL,期望响应也是 JSON HookOutput。
{
  "type": "http",
  "url": "https://example.com/hook",
  "headers": { "Authorization": "Bearer ${MY_TOKEN}" },
  "timeout": 600
}
字段必填说明
url接收 POST 的 URL
headers自定义请求头,值支持 ${ENV_VAR} 插值
allowedEnvVars限制 headers 可插值的环境变量白名单;不填则允许所有
timeout超时时间(秒),默认 600
if / once / statusMessage同 command

prompt(单次模型调用)

通过独立的单轮模型调用评估 hook 事件,模型按提示词返回 { ok, reason }ok=true 视为允许,ok=false 视为阻塞,reason 在阻塞时作为说明返回给 Agent。
{
  "type": "prompt",
  "prompt": "判断该命令是否安全,需要在不安全时返回 ok=false 并给出 reason",
  "model": "haiku",
  "timeout": 30
}
字段必填说明
prompt发送给评估模型的提示词模板,序列化后的事件 JSON 会追加在其后
model覆盖模型,不填则使用会话默认模型
timeout超时时间(秒),默认 30
if / once / statusMessage同 command
独立评估。 评估模型在独立会话中运行,仅接收你的 prompt 与当前事件数据,看不到主对话的工具调用、模型输出或之前发生的任何事情。判定条件应能基于当前事件本身得出;依赖会话历史的规则无法可靠评估,请改用维护自身状态的 command hook 或可访问文件系统的 agent hook。

agent(子 Agent 校验)

启动一个子 Agent 来核查条件。子 Agent 必须调用 StructuredOutput 工具返回 { ok: boolean, reason?: string }ok=true 视为允许,ok=false 视为阻塞。
{
  "type": "agent",
  "prompt": "审查以下变更:$ARGUMENTS",
  "tools": ["Read", "Grep"],
  "maxTurns": 50,
  "timeout": 60
}
字段必填说明
prompt校验提示词,支持 $ARGUMENTS 占位符(自动替换为 hook 输入 JSON)
tools子 Agent 允许使用的工具白名单。不填则继承全部可用工具,但会自动过滤不适合在 hook 中使用的工具(如递归调用 Agent、计划模式、交互式提问等)
maxTurns最多 agent 轮次,默认 50
model覆盖模型
timeout超时时间(秒),默认 60
if / once / statusMessage同 command
独立评估。prompt 一样,子 Agent 在独立会话中运行,看不到主对话历史;区别在于它可以使用工具检查工作目录与运行检查,因此适合需要核查真实状态(读文件、跑校验等)的场景。

matcher 与 if 匹配规则

matcher(分组级)用于过滤 hook 的触发范围。匹配的字段因事件而异(详见各事件说明),常见的有工具名、事件 trigger、来源等:
写法含义示例
不填或 "*"匹配所有所有工具都触发
精确值精确匹配"Bash" 只匹配 Bash 工具
| 分隔匹配多个值"Write|Edit" 匹配 Write 或 Edit
正则表达式正则匹配"mcp__.*" 匹配所有 MCP 工具
if(hook 条目级)则是另一种更精细的过滤,格式为 "ToolName""ToolName(arg_pattern)"
  • 工具名部分复用与 matcher 相同的工具名匹配逻辑(即支持正则或 |
  • 括号中的 arg_patternglob 通配匹配(不是正则),用于检查工具的主要参数(如 Bash 的 command、文件类工具的 file_path
示例:
if 写法含义
"Bash"工具是 Bash 时触发
"Bash(git *)"工具是 Bash 且命令以 git 开头时触发
"Edit(*.ts)"工具是 Edit 且 file_path 匹配 *.ts 时触发

Hook 脚本编写

Hook 脚本通过 stdin 接收 JSON 输入,通过 exit code 和 stdout 控制行为。本节说明所有事件通用的输入输出格式,事件特有的字段见事件清单

输入

Hook 脚本通过 stdin 接收 JSON 数据。所有事件都包含以下通用字段:
字段说明
session_id当前会话 ID
transcript_path当前 transcript 文件路径
cwd当前工作目录
hook_event_name触发的事件名称
permission_mode当前权限模式(如果该事件提供)
agent_id当前 Agent ID(如果该事件提供)
agent_type当前 Agent 类型(如果该事件提供)
不同事件会在此基础上附加额外字段(详见各事件说明)。 jq 解析输入:
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')

输出

Hook 通过 exit code 和 stdout 控制行为。

exit code

  • 0:成功;stdout 会按下文规则解析。
  • 2:阻塞;stderr 内容作为反馈传给 Agent(仅对支持阻塞的事件生效)。
  • 其他值:非阻塞错误,stdout 被忽略,stderr 写入诊断日志,主流程继续。

stdout JSON 通用字段

当 exit 0 且 stdout 是合法 JSON 时,CLI 会按以下字段解析;不是 JSON 则按纯文本处理(仅 SessionStart / UserPromptSubmit 会把纯文本作为附加上下文注入对话)。
字段说明
continuefalse 时请求停止后续执行
stopReasoncontinue: false 配合,向 Agent 说明停止原因
suppressOutputtrue 时不将 hook 输出展示给用户
systemMessage展示给用户的 hook 系统消息,不注入模型上下文
decision"allow""deny",事件相关的决策;"deny" 等价 exit 2。若需要请求用户授权("ask"),仅在 PreToolUsehookSpecificOutput.permissionDecision 中使用
reason决策原因,会展示给用户/模型
hookSpecificOutput各事件专属字段的容器(详见各事件说明)
事件特有的精细控制字段(如 PreToolUsepermissionDecisionPostToolUseupdatedToolOutput 等)放在 hookSpecificOutput 对象中。输出 hookSpecificOutput 时必须带上 hookEventName,否则整个 JSON 输出会被拒绝,TUI 显示 <hookName> hook error: hookSpecificOutput is missing required field "hookEventName"。例如:
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "ask"
  }
}

环境变量

Hook 脚本执行时可使用以下环境变量:
变量说明
QODER_PROJECT_DIR当前项目的工作目录
QODER_PLUGIN_ROOT当 hook 来自插件时,指向该插件根目录
QODER_PLUGIN_DATA当 hook 来自插件时,指向该插件的数据目录

事件清单

按用途分组列出可订阅的事件。每个事件标明 matcher 匹配的字段、stdin 的额外输入字段、是否支持阻塞,以及可用的 hookSpecificOutput 字段。

总览

事件matcher 匹配exit 2 阻塞关键输入字段
SessionStartsource(startup/resume/clear/compact/new)source, model
SessionEndreasonreason
UserPromptSubmitprompt
PreToolUse工具名tool_name, tool_input
PostToolUse工具名tool_name, tool_input, tool_response
PostToolUseFailure工具名tool_name, error, error_type
PermissionRequest工具名tool_name, tool_input
PermissionDenied工具名tool_name, tool_input, reason
Stopstop_hook_active, last_assistant_message
StopFailureerror_typeerror_type, error
SubagentStartAgent 类型agent_id, agent_type
SubagentStopAgent 类型agent_id, agent_type, stop_hook_active
PreCompacttriggertrigger, custom_instructions
PostCompacttriggertrigger, compact_summary
Notificationnotification_typenotification_type, message
InstructionsLoadedload_reasonfile_path, memory_type, load_reason
ConfigChangesource✅(policy_settings 来源除外)source, file_path
CwdChangedold_cwd, new_cwd
FileChanged文件名(basename)file_path, event
WorktreeCreate失败:非 0 exitname
WorktreeRemoveworktree_path
Elicitationmcp_server_namemcp_server_name, message, requested_schema
ElicitationResultmcp_server_namemcp_server_name, action, content

会话生命周期

SessionStart

会话开始时触发。 matcher 匹配: 会话来源
matcher 值触发场景
startup新会话启动
resume恢复已有会话
clear通过 /clear 重置
compact上下文压缩完成后
new新建会话(其他来源)
额外输入字段:
{
  "source": "startup",
  "model": "Auto"
}
hookSpecificOutput: additionalContext(注入到对话的上下文)
当返回纯文本(非 JSON)时,stdout 也会作为上下文注入对话。

SessionEnd

会话结束时触发。 matcher 匹配: 结束原因
matcher 值触发场景
clear通过 /clear 结束
resume切换到另一个会话
logout退出登录
prompt_input_exit用户退出输入(Ctrl+D 等)
bypass_permissions_disabled绕过权限模式被关闭导致结束
other其他原因
额外输入字段:
{
  "reason": "prompt_input_exit"
}

UserPromptSubmit

用户提交 Prompt 后、Agent 处理前触发。可以阻止该 Prompt 进入对话。 额外输入字段:
{
  "prompt": "帮我写一个排序函数"
}
阻塞: exit 2 拒绝该 Prompt,stderr 作为提示展示给用户。 hookSpecificOutput:
  • additionalContext:与 Prompt 一起注入对话
  • sessionTitle:建议的会话标题
当返回纯文本(非 JSON)时,stdout 也会作为上下文注入对话。

工具调用

PreToolUse

工具执行前触发。可以阻止工具执行或修改输入参数。 matcher 匹配: 工具名(如 BashWriteEditReadGlobGrep,MCP 工具名如 mcp__server__tool 额外输入字段:
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf /tmp/build"},
  "tool_use_id": "toolu_01ABC123"
}
当工具来自 MCP 时,还会附带 mcp_context(含 server_nametool_name、连接信息)和 original_request_name
阻塞: exit 2,stderr 作为错误返回给 Agent。 hookSpecificOutput:
字段说明
permissionDecision"allow" / "deny" / "ask",等价于顶层 decision,覆盖优先
permissionDecisionReason决策原因,覆盖顶层 reason
updatedInput修改后的工具输入参数(替换原始 tool_input
additionalContext注入对话的额外上下文

PostToolUse

工具执行成功后触发。 matcher 匹配: 工具名 额外输入字段:
{
  "tool_name": "Write",
  "tool_input": {"file_path": "/path/to/file.ts", "content": "..."},
  "tool_response": {"success": true, "bytes_written": 1024},
  "tool_use_id": "toolu_01ABC123"
}
tool_response 是对象,具体结构由工具决定。MCP 工具同样附带 mcp_context / original_request_name
hookSpecificOutput:
字段说明
updatedToolOutput替换工具响应(任意工具均可使用)
updatedMCPToolOutput仅替换 MCP 工具响应(优先级低于 updatedToolOutput
additionalContext注入对话的额外上下文

PostToolUseFailure

工具执行失败后触发。 matcher 匹配: 工具名 额外输入字段:
{
  "tool_name": "Bash",
  "tool_input": {"command": "npm test"},
  "tool_use_id": "toolu_01ABC123",
  "error": "Command exited with non-zero status code 1",
  "error_type": "execution_failed",
  "is_interrupt": false
}
hookSpecificOutput: additionalContext

PermissionRequest

工具执行需要用户授权时触发。可以自动允许、拒绝或修改输入。 matcher 匹配: 工具名 额外输入字段:
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf node_modules"},
  "permission_suggestions": []
}
hookSpecificOutput: decision 对象,其字段随 behavior 取值不同: behavior: "allow"(允许执行,可同时改写输入或写入持久权限):
{
  "behavior": "allow",
  "updatedInput": { "command": "..." },
  "updatedPermissions": []
}
字段说明
behavior必须是 "allow"
updatedInput修改后的工具输入(替换原始 tool_input
updatedPermissions同时写入的持久权限规则
behavior: "deny"(拒绝执行,可附加用户提示):
{
  "behavior": "deny",
  "message": "...",
  "interrupt": false
}
字段说明
behavior必须是 "deny"
message展示给用户的说明
interrupt是否打断当前操作并向用户展示
PermissionRequest 的 hook 不支持 "ask" 行为;如果希望弹窗征询用户,请使用 PreToolUsepermissionDecision: "ask"

PermissionDenied

权限分类器拒绝工具调用时触发,可请求重试。 matcher 匹配: 工具名 额外输入字段:
{
  "tool_name": "Bash",
  "tool_input": {"command": "..."},
  "tool_use_id": "toolu_01ABC123",
  "reason": "Auto mode classifier blocked this call"
}
hookSpecificOutput: retry: true 时请求重试该工具调用。

Agent 流程

Stop

主 Agent 完成响应、且无待执行工具调用时触发。可以阻止 Agent 停止,让其继续工作。 额外输入字段:
{
  "stop_hook_active": false,
  "last_assistant_message": "..."
}
字段说明
stop_hook_active当前是否正处于由 Stop hook 驱动的延续轮次(避免无限循环时使用)
last_assistant_message停止前最后一条 Assistant 消息
阻塞: exit 2,stderr 作为消息注入对话,Agent 继续工作。 hookSpecificOutput: clearContext: true 时同时清空上下文。

StopFailure

Agent 因错误意外停止时触发,仅作通知,输出与 exit code 被忽略。 matcher 匹配: error_type(如 rate_limitserver_error 等) 额外输入字段:
{
  "error_type": "rate_limit",
  "error": "...",
  "error_details": "...",
  "last_assistant_message": "..."
}
error_type 取值:rate_limit / authentication_failed / billing_error / invalid_request / server_error / max_output_tokens / unknown

SubagentStart

子 Agent 启动时触发。 matcher 匹配: Agent 类型名 额外输入字段:
{
  "agent_id": "a1b2c3d4",
  "agent_type": "task"
}
hookSpecificOutput: additionalContext

SubagentStop

子 Agent 完成时触发,可阻止其停止(与 Stop 类似)。 matcher 匹配: Agent 类型名 额外输入字段:
{
  "agent_id": "a1b2c3d4",
  "agent_type": "task",
  "stop_hook_active": false,
  "agent_transcript_path": "...",
  "last_assistant_message": "..."
}
阻塞: exit 2,stderr 作为消息注入子 Agent 对话。 hookSpecificOutput: clearContext: true 时同时清空子 Agent 上下文。

上下文压缩

PreCompact

上下文压缩前触发,可以阻止压缩。 matcher 匹配: 触发方式
matcher 值触发场景
manual用户手动执行 /compact
auto上下文窗口接近上限时自动触发
额外输入字段:
{
  "trigger": "manual",
  "custom_instructions": "保留所有工具调用结果"
}
阻塞: exit 2 阻止本次压缩。

PostCompact

上下文压缩完成后触发。 matcher 匹配: 触发方式(同 PreCompact) 额外输入字段:
{
  "trigger": "manual",
  "compact_summary": "压缩摘要..."
}
hookSpecificOutput: additionalContext

通知与提示

Notification

发出用户提示(权限请求、空闲提示、Elicitation 等)时触发。 matcher 匹配: 通知类型
matcher 值触发场景
permission_prompt工具权限请求
idle_prompt空闲提示
auth_success鉴权成功
elicitation_dialogMCP elicitation 对话框出现
elicitation_response用户对 elicitation 做出回应
elicitation_completeelicitation 流程完成
额外输入字段:
{
  "notification_type": "permission_prompt",
  "message": "Agent is requesting permission to run: rm -rf node_modules",
  "title": "Permission Required",
  "details": {}
}
hookSpecificOutput: additionalContext

上下文与配置加载

InstructionsLoaded

指令/记忆文件被加载时触发,仅作通知,输出与 exit code 被忽略。 matcher 匹配: load_reason(如 session_startinclude 等) 额外输入字段:
{
  "file_path": "/abs/path/AGENTS.md",
  "memory_type": "project",
  "load_reason": "session_start",
  "globs": ["**/AGENTS.md"],
  "trigger_file_path": "...",
  "parent_file_path": "..."
}
load_reason 取值:session_start / nested_traversal / path_glob_match / include / compact

ConfigChange

会话期间配置文件发生变更时触发。 matcher 匹配: 配置来源
matcher 值触发场景
user_settings用户级 ~/.qoder/settings.json
project_settings项目级 ${project}/.qoder/settings.json
local_settings项目本地 ${project}/.qoder/settings.local.json
policy_settings策略配置
skillsSkill 目录变更
agents自定义 Agent 目录变更
额外输入字段:
{
  "source": "user_settings",
  "file_path": "/abs/path/settings.json"
}
阻塞: exit 2 阻止该变更应用到当前会话。policy_settings 来源例外:hook 仍会触发用于审计,但变更会强制生效,不可被阻止。

工作目录与文件

CwdChanged

工作目录变更时触发。 额外输入字段:
{
  "old_cwd": "/old",
  "new_cwd": "/new"
}
hookSpecificOutput:
字段说明
additionalContext注入对话的上下文
watchPaths注册到 FileChanged 监听器的绝对路径列表

FileChanged

被监听的文件变化时触发。 matcher 匹配: 变更文件的 basename(支持精确匹配、| 多值、正则) 额外输入字段:
{
  "file_path": "/abs/path/file.ts",
  "event": "change"
}
event 取值:change / add / unlink hookSpecificOutput: additionalContextwatchPaths(同 CwdChanged)

Worktree 隔离

WorktreeCreate

需要创建一个隔离的工作树时触发。Hook 必须返回 worktree 的绝对路径,任何非零 exit code 视为失败。 额外输入字段:
{
  "name": "feature-x"
}
返回路径:把绝对路径写入 stdout,或在 hookSpecificOutput.worktreePath 中提供。

WorktreeRemove

移除工作树时触发,仅作通知,失败仅展示 stderr。 额外输入字段:
{
  "worktree_path": "/abs/path/worktree"
}

MCP 交互

Elicitation

MCP server 请求用户输入(elicitation)时触发。Hook 可自动接受、拒绝或取消。 matcher 匹配: mcp_server_name 额外输入字段:
{
  "mcp_server_name": "my-server",
  "message": "请确认操作",
  "mode": "...",
  "url": "...",
  "elicitation_id": "...",
  "requested_schema": {}
}
阻塞: exit 2 拒绝该 elicitation。 hookSpecificOutput:
字段说明
action"accept" / "decline" / "cancel"
contentaccept 时提供的输入内容

ElicitationResult

用户对 elicitation 完成响应后触发,可覆盖响应。 matcher 匹配: mcp_server_name 额外输入字段:
{
  "mcp_server_name": "my-server",
  "action": "accept",
  "content": {},
  "mode": "...",
  "elicitation_id": "..."
}
阻塞: exit 2 把 action 改写为 decline hookSpecificOutput: actioncontent(覆盖原响应)

实用场景

桌面通知提醒

当 Agent 完成任务或需要授权时,弹出桌面通知。 脚本 ~/.qoder/hooks/notify.sh(macOS):
#!/bin/bash
input=$(cat)
ntype=$(echo "$input" | jq -r '.notification_type')

if [ "$ntype" = "permission_prompt" ]; then
  osascript -e 'display notification "任务需要授权" with title "Qoder CLI"'
else
  osascript -e 'display notification "收到新通知" with title "Qoder CLI"'
fi

exit 0
配置:
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.qoder/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}

写文件后自动 Lint

每次 Agent 写入或编辑文件后,自动执行 lint 检查。 脚本 ${project}/.qoder/hooks/auto-lint.sh
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

case "$file_path" in
  *.js|*.ts|*.jsx|*.tsx)
    npx eslint "$file_path" --fix 2>/dev/null
    ;;
esac

exit 0
配置:事件 PostToolUse,matcher Write|Edit,command .qoder/hooks/auto-lint.sh

让 Agent 继续工作

在 Agent 停止时检查是否还有未完成的任务,如果有则注入消息让 Agent 继续。 脚本 ~/.qoder/hooks/check-continue.sh
#!/bin/bash
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
  echo "检测到未提交的变更,请完成 git commit" >&2
  exit 2
fi

exit 0
配置:事件 Stop,command ~/.qoder/hooks/check-continue.sh