Hooks
在 CrabCode 关键事件触发时跑你自己的逻辑:shell 命令、HTTP 回调、LLM 验证。配置走 settings.json。
是什么
Hook 是挂在 CrabCode 生命周期事件上的回调。事件触发时,CrabCode 把一段 JSON 通过 stdin(或 HTTP POST)喂给你的钩子;返回值决定后续动作——放行、阻止、修改输入、或者把额外上下文喂回模型。
四种钩子类型:
type | 形态 | 适合 |
|---|---|---|
command | 跑一个 shell 命令 | 跑 lint、写日志、阻止危险操作 |
http | POST 到一个 URL | 集中式审计、转发到 SaaS |
prompt | 用小模型评一段 prompt | 轻量语义判断("这次写的是 secret 吗") |
agent | 起一个 agentic verifier | 复杂任务后验证("测试真的跑过了吗") |
何时会看到这个文档
/hooks命令的"添加"页面(只读,提示你直接编辑 settings.json)- settings.json 校验报错指向
hooks字段
支持的事件
| 事件 | 何时触发 |
|---|---|
SessionStart | 会话启动 |
SessionEnd | 会话结束(超时极紧,默认 1.5s) |
Setup | --init / --maintenance 启动时一次 |
UserPromptSubmit | 用户回车提交输入 |
PreToolUse | 模型请求调用工具,在执行前 |
PostToolUse | 工具执行成功后 |
PostToolUseFailure | 工具执行失败 / 超时 / 中断后 |
Stop / StopFailure | 模型回合结束 / 因 API 错误结束 |
Notification | 等待用户输入等系统通知 |
SubagentStart / SubagentStop | 子智能体启停 |
PermissionRequest / PermissionDenied | 权限询问 / 拒绝 |
PreCompact / PostCompact | 对话压缩前 / 后 |
CwdChanged / FileChanged | 工作目录或被监视文件变化 |
WorktreeCreate / WorktreeRemove | git worktree 创建 / 删除 |
Elicitation / ElicitationResult | MCP elicitation 流程 |
TeammateIdle / TaskCreated / TaskCompleted | 多智能体协作场景 |
ConfigChange / InstructionsLoaded | settings 变更 / CRABCODE.md 加载 |
完整可用事件以
/hooks里的下拉列表为准——SDK 与运行时同源。
配置示例
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.crabcode/hooks/check-bash.sh",
"if": "Bash(git push *)",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "prettier --write \"$FILE_PATH\"" }
]
}
],
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "验证最近的改动跑过了测试,否则报错。",
"timeout": 60
}
]
}
]
}
}{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.crabcode/hooks/check-bash.sh",
"if": "Bash(git push *)",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "prettier --write \"$FILE_PATH\"" }
]
}
],
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "验证最近的改动跑过了测试,否则报错。",
"timeout": 60
}
]
}
]
}
}matcher:字符串/正则,匹配 hook 事件相关的值(PreToolUse/PostToolUse 时匹配工具名)。省略 = 匹配所有if:用权限规则语法过滤(如Bash(git push *)),不匹配就不 spawn,省 fork 成本timeout:单次执行秒数,超时会被 killshell:command 类型可选bash/powershell(默认bash,跟随$SHELL)
Hook 协议(command 类型)
- stdin:JSON 对象,事件特定字段。常见键:
tool_name、tool_input、tool_use_id(PreToolUse / PostToolUse / PostToolUseFailure)session_id、hook_event_name- PostToolUse 还有
response;PostToolUseFailure 还有error、error_type、is_interrupt、is_timeout - PreCompact 有压缩详情;UserPromptSubmit 有原始 prompt 文本
- stdout:可选 JSON 响应。常用形态:
{"continue": false, "stopReason": "..."}终止本轮{"decision": "block", "reason": "..."}阻止动作{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "..."}}显式拒绝权限{"async": true, "asyncTimeout": 30}切换为后台异步
- 退出码语义(每个事件略有差异,以
/hooks内说明为准):0— 通过;stdout 内容按事件决定是否展示2— 阻塞 / 把 stderr 反馈给模型;常用于 PreToolUse、UserPromptSubmit、Stop- 其他 — 报错给用户,会话继续
HTTP 钩子把同样的 JSON POST 到
url;headers支持$VAR_NAME占位符引用allowedEnvVars白名单里的环境变量。
完整 stdin / stdout schema
stdin 公共字段(每个事件都有):session_id、hook_event_name、cwd、transcript_path(当前会话 transcript 文件路径)。
事件特定字段:
PreToolUse/PostToolUse/PostToolUseFailure:tool_name、tool_input、tool_use_id;PostToolUse多response;PostToolUseFailure多error/error_type/is_interrupt/is_timeoutUserPromptSubmit:prompt(用户输入原文)PreCompact/PostCompact:trigger、custom_instructions等FileChanged:changed_paths、change_kindPermissionRequest:待批的工具 / 权限规则InstructionsLoaded:file_path、memory_type(User / Project / Local / Managed)、load_reason(session_start / nested_traversal / path_glob_match / include / compact)、globs?、trigger_file_path?、parent_file_path?
stdout 响应字段(全部可选;不输出 = 默认通过):continue(false 终止本轮)、suppressOutput(隐藏 stdout)、stopReason、decision("approve" / "block")、reason、systemMessage(用户侧警告)、async + asyncTimeout(后台异步)、hookSpecificOutput(事件维度高级控制)。
hookSpecificOutput 常用形态:
PreToolUse:permissionDecision(allow/deny/ask)、permissionDecisionReason、updatedInput(改写工具入参)、additionalContextUserPromptSubmit/SessionStart/Setup/SubagentStart/PostToolUse/PostToolUseFailure/Notification:additionalContextSessionStart/CwdChanged/FileChanged:watchPaths(注册要监听的绝对路径)PermissionRequest:decision: { behavior: "allow" | "deny", ... }Elicitation/ElicitationResult:action(accept/decline/cancel) +contentWorktreeCreate:worktreePath
Hook 失败行为
CrabCode 对 hook 失败是软失败:
- 退出码 0 → 通过
- 退出码 2 → 阻塞 / 把 stderr 反馈给模型(
PreToolUse/UserPromptSubmit/Stop/TeammateIdle/TaskCreated/TaskCompleted等) - 其他非零 → 标记为
non_blocking_error:用户看到一条系统提示(带命令名与 stderr),会话不会中断 - 超时 → 进程被 kill,按非零退出处理
- JSON 输出格式错 → 视作
non_blocking_error,stderr 提示 schema 错误
只有退出码 2(或显式 {"decision":"block"} / permissionDecision:"deny")才会真的拦住模型;其他失败模式只打日志、继续走。
调试
- 打开 debug 日志:用
--debug启动 CrabCode,会把 hook 执行细节(命令行、stdout、stderr、退出码、耗时)写进~/.crabcode/debug/<session-id>.txt(用CRABCODE_DEBUG_LOGS_DIR改路径;-d2e/--debug-to-stderr直输 stderr) - 看历史最近一条:
~/.crabcode/debug/latest是符号链接,永远指向上一次 debug 日志 - 看 hook 视角的 stdin:在 hook 脚本里
cat > /tmp/hook-input.json然后exit 0,离线分析 JSON 结构 - hook 卡住:CrabCode 主进程对单个 hook 默认
TOOL_HOOK_EXECUTION_TIMEOUT_MS = 10min;SessionEnd默认 1.5s(用CRABCODE_SESSIONEND_HOOKS_TIMEOUT_MS调);超时直接 kill - HTTP hook 失败排查:把
url临时换成httpbin.org/post或本地 listener,确认 payload 形状;headers里的$VAR_NAME只解析allowedEnvVars白名单里的变量
真实示例
示例 1:拦截 git push --force 到 main——PreToolUse 上挂一个 hook,读 stdin 取 tool_input.command,匹配到 force-push 就 exit 2:
# ~/.crabcode/hooks/guard-force-push.sh
#!/usr/bin/env bash
payload=$(cat)
cmd=$(echo "$payload" | jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -Eq 'git push.*(--force|-f)\s.*(main|master)'; then
echo "禁止 force-push 到 main/master" >&2
exit 2
fi# ~/.crabcode/hooks/guard-force-push.sh
#!/usr/bin/env bash
payload=$(cat)
cmd=$(echo "$payload" | jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -Eq 'git push.*(--force|-f)\s.*(main|master)'; then
echo "禁止 force-push 到 main/master" >&2
exit 2
fi配上 settings.json:
{ "hooks": { "PreToolUse": [
{ "matcher": "Bash", "hooks": [
{ "type": "command", "command": "~/.crabcode/hooks/guard-force-push.sh", "timeout": 3 }
]}
]}}{ "hooks": { "PreToolUse": [
{ "matcher": "Bash", "hooks": [
{ "type": "command", "command": "~/.crabcode/hooks/guard-force-push.sh", "timeout": 3 }
]}
]}}示例 2:写后自动 format——PostToolUse 匹 Edit|Write,从 tool_input.file_path 拿到刚写的文件路径丢 prettier:
{ "hooks": { "PostToolUse": [
{ "matcher": "Edit|Write", "hooks": [
{ "type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path'); [ -n \"$FILE\" ] && prettier --write \"$FILE\" 2>&1 || true",
"timeout": 10 }
]}
]}}{ "hooks": { "PostToolUse": [
{ "matcher": "Edit|Write", "hooks": [
{ "type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path'); [ -n \"$FILE\" ] && prettier --write \"$FILE\" 2>&1 || true",
"timeout": 10 }
]}
]}}限制与注意
- PreToolUse 默认 10 分钟超时,但执行期间会卡住会话——别在里面跑重活
- 绝对路径优先:
~在某些环境下不展开 - 谨慎放权:hook 跑在你的 shell 用户下,等同于
bash - PostToolUseFailure 是兜底事件:API 错误 / 工具超时 / Esc 中断都会走这里,可在此清场(删临时文件、回滚 git stash)
- async / asyncRewake:长任务用
"async": true让 hook 后台跑,不阻塞会话;asyncRewake在退出码 2 时唤醒模型继续