hook-authoring

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hook Authoring — Official Best Practices

Hook 开发——官方最佳实践

What Hooks Do

钩子的作用

Hooks are deterministic shell commands (or LLM prompts) that execute at specific lifecycle points. They provide guaranteed behavior — not relying on the LLM to choose to run them.
Hooks是在特定生命周期节点执行的确定性shell命令(或LLM提示词)。它们提供可预期的行为——无需依赖LLM自主选择是否运行。

Configuration Locations

配置位置

LocationScopeShareable
~/.claude/settings.json
All projectsNo
.claude/settings.json
Single projectYes (commit)
.claude/settings.local.json
Single projectNo (gitignored)
Agent/skill frontmatterWhile component activeYes
Plugin
hooks/hooks.json
When plugin enabledYes
位置作用范围可共享性
~/.claude/settings.json
所有项目
.claude/settings.json
单个项目是(可提交到版本库)
.claude/settings.local.json
单个项目否(已加入git忽略)
Agent/skill 前置信息组件激活期间
插件
hooks/hooks.json
插件启用时

Hook Types

钩子类型

TypeHow it worksUse when
command
Runs shell command, reads stdin JSON, uses exit codesDeterministic validation, formatting, logging
prompt
Single-turn LLM call, returns
{ok, reason}
Judgment-based decisions without tool access
agent
Multi-turn subagent with tool accessVerification requiring file reads or commands
http
POSTs event data to URL endpointExternal service integration, audit logging
类型工作方式适用场景
command
运行shell命令,读取标准输入JSON,使用退出码确定性验证、格式化、日志记录
prompt
单轮LLM调用,返回
{ok, reason}
无需工具访问的基于判断的决策
agent
具备工具访问权限的多轮子智能体需要读取文件或执行命令的验证操作
http
将事件数据POST到URL端点外部服务集成、审计日志

Hook Events

钩子事件

See quick-ref/events-reference.md for full input/output schemas.
EventMatcher inputCan block?Common use
SessionStart
startup/resume/clear/compactNoRe-inject context after compaction
UserPromptSubmit
(none)YesValidate/transform user input
PreToolUse
Tool nameYesBlock commands, validate operations
PermissionRequest
Tool nameYesAuto-allow/deny permissions
PostToolUse
Tool nameNo*Auto-format files, logging
PostToolUseFailure
Tool nameNoError handling
Notification
Notification typeNoDesktop alerts
SubagentStart
Agent typeNoSetup before agent runs
SubagentStop
Agent typeNoCleanup after agent
Stop
(none)YesVerify completeness
ConfigChange
Config sourceYesAudit, block unauthorized changes
PreCompact
manual/autoNoSave context before compaction
SessionEnd
Exit reasonNoCleanup
*PostToolUse
Stop
hooks can return
{"decision": "block"}
to keep Claude working.
查看quick-ref/events-reference.md获取完整的输入/输出 schema。
事件匹配器输入可阻止操作?常见用途
SessionStart
startup/resume/clear/compact压缩后重新注入上下文
UserPromptSubmit
验证/转换用户输入
PreToolUse
工具名称阻止命令、验证操作
PermissionRequest
工具名称自动允许/拒绝权限
PostToolUse
工具名称否*自动格式化文件、日志记录
PostToolUseFailure
工具名称错误处理
Notification
通知类型桌面提醒
SubagentStart
智能体类型智能体运行前的初始化
SubagentStop
智能体类型智能体运行后的清理
Stop
验证完成状态
ConfigChange
配置来源审计、阻止未授权更改
PreCompact
manual/auto压缩前保存上下文
SessionEnd
退出原因清理操作
*PostToolUse的
Stop
钩子可返回
{"decision": "block"}
以维持Claude的运行状态。

Configuration Format

配置格式

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Input/Output Protocol

输入/输出协议

Input (stdin JSON)

输入(标准输入JSON)

Every hook receives JSON on stdin with common fields + event-specific data:
json
{
  "session_id": "abc123",
  "cwd": "/path/to/project",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" }
}
每个钩子都会通过标准输入接收包含通用字段+事件特定数据的JSON:
json
{
  "session_id": "abc123",
  "cwd": "/path/to/project",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" }
}

Output (exit codes)

输出(退出码)

Exit codeEffect
0
Allow — action proceeds. Stdout added to context (SessionStart, UserPromptSubmit)
2
Block — action cancelled. Stderr sent to Claude as feedback
OtherAllow — stderr logged (visible in verbose mode Ctrl+O)
退出码效果
0
允许——操作继续执行。标准输出内容会添加到上下文(SessionStart、UserPromptSubmit事件)
2
阻止——操作被取消。标准错误内容会作为反馈发送给Claude
其他允许——标准错误内容会被记录(可在详细模式Ctrl+O中查看)

Structured JSON output (exit 0 + JSON on stdout)

结构化JSON输出(退出码0 + 标准输出JSON)

json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead of grep"
  }
}
PreToolUse decisions:
"allow"
,
"deny"
,
"ask"
.
json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead of grep"
  }
}
PreToolUse的决策值:
"allow"
"deny"
"ask"

Common Patterns

常见模式

Auto-format after edits

编辑后自动格式化

json
{
  "PostToolUse": [{
    "matcher": "Edit|Write",
    "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
  }]
}
json
{
  "PostToolUse": [{
    "matcher": "Edit|Write",
    "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
  }]
}

Block protected files

阻止修改受保护文件

bash
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
for pattern in ".env" "package-lock.json" ".git/"; do
  if [[ "$FILE" == *"$pattern"* ]]; then
    echo "Blocked: matches protected pattern '$pattern'" >&2
    exit 2
  fi
done
exit 0
bash
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
for pattern in ".env" "package-lock.json" ".git/"; do
  if [[ "$FILE" == *"$pattern"* ]]; then
    echo "Blocked: matches protected pattern '$pattern'" >&2
    exit 2
  fi
done
exit 0

Re-inject context after compaction

压缩后重新注入上下文

json
{
  "SessionStart": [{
    "matcher": "compact",
    "hooks": [{ "type": "command", "command": "echo 'Reminder: use Bun, not npm. Run tests before commits.'" }]
  }]
}
json
{
  "SessionStart": [{
    "matcher": "compact",
    "hooks": [{ "type": "command", "command": "echo 'Reminder: use Bun, not npm. Run tests before commits.'" }]
  }]
}

Notification on idle

闲置时发送通知

json
{
  "Notification": [{
    "matcher": "",
    "hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude needs attention\" with title \"Claude Code\"'" }]
  }]
}
json
{
  "Notification": [{
    "matcher": "",
    "hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude needs attention\" with title \"Claude Code\"'" }]
  }]
}

Stop Hook Infinite Loop Prevention

阻止Hook无限循环

Always check
stop_hook_active
to avoid loops:
bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0  # Let Claude stop
fi
请始终检查
stop_hook_active
以避免循环:
bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0  # 允许Claude停止
fi

... your logic

... 你的业务逻辑

undefined
undefined

Anti-Patterns

反模式

Anti-PatternFix
Shell profile
echo
breaks JSON
Wrap in
if [[ $- == *i* ]]
Stop hook without loop guardCheck
stop_hook_active
field
Using PostToolUse to undo actionsToo late — use PreToolUse to block instead
Relying on PermissionRequest in headless modeDoesn't fire in
-p
mode. Use PreToolUse
反模式修复方案
Shell配置文件中的
echo
破坏JSON格式
使用
if [[ $- == *i* ]]
包裹
Stop Hook未添加循环防护检查
stop_hook_active
字段
使用PostToolUse撤销操作为时已晚——请改用PreToolUse阻止操作
在无头模式下依赖PermissionRequest该模式下不会触发(-p参数)。请改用PreToolUse

Checklist

检查清单

  • Correct event chosen for the use case
  • Matcher pattern tested (case-sensitive, regex)
  • Script is executable (
    chmod +x
    )
  • Uses
    jq
    for JSON parsing (or Python/Node)
  • Exit code 2 for blocking, 0 for allowing
  • Stop hooks check
    stop_hook_active
  • Tested with sample JSON piped to stdin
  • Hook script uses absolute paths or
    $CLAUDE_PROJECT_DIR
  • 为使用场景选择了正确的事件
  • 已测试匹配器模式(区分大小写,支持正则)
  • 脚本具备可执行权限(
    chmod +x
  • 使用
    jq
    解析JSON(或Python/Node)
  • 使用退出码2表示阻止,0表示允许
  • Stop Hook检查了
    stop_hook_active
  • 通过管道传入示例JSON进行了测试
  • Hook脚本使用绝对路径或
    $CLAUDE_PROJECT_DIR

Reference

参考资料

  • All events with input/output schemas
  • Hook types comparison
  • 包含输入/输出schema的所有事件
  • 钩子类型对比