Hooks 参考
Hooks 是在 Claude Code 事件发生时自动执行的脚本,用于自动化、校验、权限管理和自定义工作流。
概览
Hooks 是在 Claude Code 特定事件发生时自动执行的动作(shell 命令、HTTP webhook、LLM 提示词、MCP 工具调用或 subagent 评估)。它们通过 JSON 输入接收数据,通过退出码和 JSON 输出返回结果。
核心特性:
- 事件驱动的自动化
- 基于 JSON 的输入/输出
- 支持
command、http、mcp_tool、prompt和agent五种 hook 类型 - 支持模式匹配,针对特定工具触发
配置
Hooks 在 settings 文件中配置:
~/.claude/settings.json— 用户级设置(所有项目).claude/settings.json— 项目级设置(可共享、可提交).claude/settings.local.json— 本地项目设置(不提交)- 托管策略 — 组织级设置
- 插件
hooks/hooks.json— 插件范围的 hooks - Skill/Agent frontmatter — 组件生命周期 hooks
基本配置结构
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
]
}
]
}
}关键字段:
| 字段 | 说明 | 示例 |
|---|---|---|
matcher |
匹配工具名的模式(区分大小写) | "Write"、"Edit|Write"、"*" |
hooks |
hook 定义数组 | [{ "type": "command", ... }] |
type |
hook 类型:"command"(bash)、"prompt"(LLM)、"http"(webhook)、"mcp_tool"(MCP 工具调用,v2.1.118+)或 "agent"(subagent) |
"command" |
command |
要执行的 shell 命令 | "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh" |
timeout |
可选超时秒数(默认 60) | 30 |
once |
设为 true 则每个会话只运行一次 |
true |
匹配模式
| 模式 | 说明 | 示例 |
|---|---|---|
| 精确字符串 | 匹配特定工具 | "Write" |
| 正则表达式 | 匹配多个工具 | "Edit|Write" |
| 通配符 | 匹配所有工具 | "*" 或 "" |
| MCP 工具 | 服务器和工具模式 | "mcp__memory__.*" |
InstructionsLoaded matcher 值:
| Matcher 值 | 说明 |
|---|---|
session_start |
会话启动时加载的指令 |
nested_traversal |
嵌套目录遍历时加载的指令 |
path_glob_match |
通过路径 glob 模式匹配加载的指令 |
Hook 类型
Claude Code 支持五种 hook 类型:
Command Hooks
默认的 hook 类型。执行 shell 命令,通过 JSON stdin/stdout 和退出码通信。
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py\"",
"timeout": 60
}HTTP Hooks
v2.1.63 新增。
远程 webhook 端点,接收与 command hooks 相同的 JSON 输入。HTTP hooks 以 POST 方式将 JSON 发送到 URL 并接收 JSON 响应。启用沙盒时,HTTP hooks 会通过沙盒路由。URL 中的环境变量插值需要显式 allowedEnvVars 列表以确保安全。
{
"hooks": {
"PostToolUse": [{
"type": "http",
"url": "https://my-webhook.example.com/hook",
"matcher": "Write"
}]
}
}关键属性:
"type": "http"— 标识为 HTTP hook"url"— webhook 端点 URL- 启用沙盒时通过沙盒路由
- URL 中使用环境变量插值需要显式
allowedEnvVars列表
Prompt Hooks
LLM 评估的提示词,hook 内容是一段由 Claude 评估的提示词。主要用于 Stop 和 SubagentStop 事件,实现智能任务完成检查。
{
"type": "prompt",
"prompt": "评估 Claude 是否完成了所有请求的任务。",
"timeout": 30
}LLM 评估提示词后返回结构化决策(详见基于提示词的 Hooks)。
MCP Tool Hooks
v2.1.118 新增。
mcp_tool 类型直接调用已配置的 MCP 工具;配置中引用 MCP 服务器和工具名称,而非 shell 命令或 URL。当校验或响应逻辑已存在于你配置的 MCP 服务器中时,这非常有用。
{
"matcher": "Edit",
"hooks": [{
"type": "mcp_tool",
"server": "my-mcp-server",
"tool": "validate_edit"
}]
}关键属性:
"type": "mcp_tool"— 标识为 MCP 工具 hook"server"— 已配置的 MCP 服务器名称"tool"— 该服务器上要调用的工具名称
hook 输入(工具名、工具输入、会话上下文)会作为 MCP 工具的参数传递。详见 MCP 服务器配置。
Agent Hooks
基于 subagent 的验证 hook,生成一个专门的 agent 来评估条件或执行复杂检查。与 prompt hooks(单轮 LLM 评估)不同,agent hooks 可以使用工具并执行多步推理。
{
"type": "agent",
"prompt": "验证代码变更是否符合我们的架构指南。检查相关设计文档并进行对比。",
"timeout": 120
}关键属性:
"type": "agent"— 标识为 agent hook"prompt"— subagent 的任务描述- agent 可以使用工具(Read、Grep、Bash 等)执行评估
- 返回与 prompt hooks 类似的结构化决策
Hook 事件
Claude Code 支持 29 个 hook 事件:
| 事件 | 触发时机 | Matcher 输入 | 可阻止 | 常见用途 |
|---|---|---|---|---|
| SessionStart | 会话开始/恢复/清除/压缩 | startup/resume/clear/compact | 否 | 环境设置 |
| Setup | 初始环境设置(每个会话一次) | (无) | 否 | 安装工具、安装依赖 |
| InstructionsLoaded | CLAUDE.md 或规则文件加载后 | (无) | 否 | 修改/过滤指令 |
| UserPromptSubmit | 用户提交提示词 | (无) | 是 | 校验提示词 |
| UserPromptExpansion | 用户提示词被扩展(如 @ 提及、斜杠命令解析) |
(无) | 是 | 转换或检查扩展后的提示词 |
| PreToolUse | 工具执行前 | 工具名 | 是(allow/deny/ask) | 校验、修改输入 |
| PermissionRequest | 权限对话框显示 | 工具名 | 是 | 自动批准/拒绝 |
| PermissionDenied | 用户拒绝权限请求 | 工具名 | 否 | 日志、分析、策略执行 |
| PostToolUse | 工具成功后 | 工具名 | 否 | 添加上下文、反馈 |
| PostToolUseFailure | 工具执行失败 | 工具名 | 否 | 错误处理、日志 |
| PostToolBatch | 一批工具使用完成后 | (无) | 否 | 聚合报告、批量校验 |
| Notification | 通知发送 | 通知类型 | 否 | 自定义通知 |
| SubagentStart | subagent 启动 | agent 类型名 | 否 | subagent 设置 |
| SubagentStop | subagent 完成 | agent 类型名 | 是 | subagent 验证 |
| Stop | Claude 完成回复 | (无) | 是 | 任务完成检查 |
| StopFailure | API 错误结束回合 | (无) | 否 | 错误恢复、日志 |
| TeammateIdle | agent 团队成员空闲 | (无) | 是 | 队友协调 |
| TaskCompleted | 任务标记完成 | (无) | 是 | 任务后操作 |
| TaskCreated | 通过 TaskCreate 创建任务 | (无) | 否 | 任务跟踪、日志 |
| ConfigChange | 配置文件变更 | (无) | 是(策略除外) | 响应配置更新 |
| CwdChanged | 工作目录变更 | (无) | 否 | 目录特定设置 |
| FileChanged | 监视的文件变更 | (无) | 否 | 文件监控、重建 |
| PreCompact | 上下文压缩前 | manual/auto | 否 | 压缩前操作 |
| PostCompact | 压缩完成后 | (无) | 否 | 压缩后操作 |
| WorktreeCreate | 工作树创建中 | (无) | 是(路径返回) | 工作树初始化 |
| WorktreeRemove | 工作树移除中 | (无) | 否 | 工作树清理 |
| Elicitation | MCP 服务器请求用户输入 | (无) | 是 | 输入校验 |
| ElicitationResult | 用户响应 elicitation | (无) | 是 | 响应处理 |
| SessionEnd | 会话终止 | (无) | 否 | 清理、最终日志 |
PostToolUse 耗时(v2.1.119):
PostToolUse和PostToolUseFailurehook 输入现在包含duration_ms— 详见 PostToolUse 部分。
PreToolUse
在 Claude 创建工具参数之后、处理之前运行。用于校验或修改工具输入。
配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
}
]
}
]
}
}常用 matcher: Task、Bash、Glob、Grep、Read、Edit、Write、WebFetch、WebSearch
输出控制:
permissionDecision:"allow"、"deny"或"ask"permissionDecisionReason:决策说明updatedInput:修改后的工具输入参数
PostToolUse
在工具完成后立即运行。用于验证、日志记录或向 Claude 提供上下文。
配置:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py"
}
]
}
]
}
}输出控制:
"block"决策会向 Claude 提示反馈additionalContext:为 Claude 添加的上下文
额外输入字段(v2.1.119):
| 字段 | 类型 | 说明 |
|---|---|---|
duration_ms |
number | 工具执行耗时(毫秒)。不包括权限提示和 PreToolUse hook 执行的时间。PostToolUse 和 PostToolUseFailure hook 均可用。 |
UserPromptSubmit
在用户提交提示词时运行,在 Claude 处理之前。
配置:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py"
}
]
}
]
}
}输出控制:
decision:"block"阻止处理reason:阻止原因说明additionalContext:添加到提示词的上下文
Stop 和 SubagentStop
在 Claude 完成回复(Stop)或 subagent 完成(SubagentStop)时运行。支持基于提示词的评估,实现智能任务完成检查。
额外输入字段: Stop 和 SubagentStop hook 的 JSON 输入中都包含 last_assistant_message 字段,包含 Claude 或 subagent 停止前的最后一条消息,可用于评估任务完成情况。
配置:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "评估 Claude 是否完成了所有请求的任务。",
"timeout": 30
}
]
}
]
}
}SubagentStart
在 subagent 开始执行时运行。matcher 输入是 agent 类型名,允许 hook 针对特定的 subagent 类型。
配置:
{
"hooks": {
"SubagentStart": [
{
"matcher": "code-review",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-init.sh"
}
]
}
]
}
}SessionStart
在会话启动或恢复时运行。可以持久化环境变量。
Matcher: startup、resume、clear、compact
特殊功能: 使用 CLAUDE_ENV_FILE 持久化环境变量(在 CwdChanged 和 FileChanged hook 中也可用):
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
fi
exit 0SessionEnd
在会话结束时运行,用于清理或最终日志记录。无法阻止终止。
reason 字段值:
clear— 用户清除了会话logout— 用户登出prompt_input_exit— 用户通过提示输入退出other— 其他原因
配置:
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-cleanup.sh\""
}
]
}
]
}
}Notification 事件
更新后的通知事件 matcher:
permission_prompt— 权限请求通知idle_prompt— 空闲状态通知auth_success— 认证成功elicitation_dialog— 向用户显示的对话框
组件作用域 Hooks
Hooks 可以附加到特定组件(skills、agents、commands)的 frontmatter 中:
在 SKILL.md、agent.md 或 command.md 中:
---
name: secure-operations
description: 执行带安全检查的操作
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/check.sh"
once: true # 每个会话只运行一次
---组件 hooks 支持的事件: PreToolUse、PostToolUse、Stop
这允许在使用 hook 的组件中直接定义 hook,保持相关代码在一起。
Subagent Frontmatter 中的 Hooks
当在 subagent 的 frontmatter 中定义 Stop hook 时,它会自动转换为作用于该 subagent 的 SubagentStop hook。这确保 stop hook 只在该特定 subagent 完成时触发,而不是在主会话停止时触发。
---
name: code-review-agent
description: 自动化代码审查 subagent
hooks:
Stop:
- hooks:
- type: prompt
prompt: "验证代码审查是否全面完整。"
# 上面的 Stop hook 会自动为该 subagent 转换为 SubagentStop
---PermissionRequest 事件
使用自定义输出格式处理权限请求:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow|deny",
"updatedInput": {},
"message": "自定义消息",
"interrupt": false
}
}
}Hook 输入和输出
JSON 输入(通过 stdin)
所有 hooks 通过 stdin 接收 JSON 输入:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.js",
"content": "..."
},
"tool_use_id": "toolu_01ABC123...",
"agent_id": "agent-abc123",
"agent_type": "main",
"worktree": "/path/to/worktree",
"effort": { "level": "medium" }
}常用字段:
| 字段 | 说明 |
|---|---|
session_id |
唯一会话标识符 |
transcript_path |
会话记录文件路径 |
cwd |
当前工作目录 |
hook_event_name |
触发 hook 的事件名称 |
agent_id |
运行此 hook 的 agent 标识符 |
agent_type |
agent 类型("main"、subagent 类型名等) |
worktree |
git 工作树路径(如 agent 在工作树中运行) |
effort.level |
(v2.1.133+)当前 effort 级别:low、medium、high、xhigh 或 max |
退出码
| 退出码 | 含义 | 行为 |
|---|---|---|
| 0 | 成功 | 继续,解析 JSON stdout |
| 2 | 阻塞性错误 | 阻止操作,stderr 显示为错误 |
| 其他 | 非阻塞性错误 | 继续,verbose 模式下显示 stderr |
JSON 输出(stdout,退出码 0)
{
"continue": true,
"stopReason": "停止时的可选消息",
"suppressOutput": false,
"systemMessage": "可选的警告消息",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "文件在允许的目录中",
"updatedInput": {
"file_path": "/modified/path.js"
}
}
}作用域(v2.1.121+):
hookSpecificOutput.updatedToolOutput现在对所有工具生效,不仅限于 MCP 工具。Bash、Edit、Read等工具的PostToolUsehook 可以在 Claude 看到之前重写工具输出 — 适用于编辑敏感信息、标准化 diff 或过滤冗余命令输出。示例(去除Bash输出中的 ANSI 颜色码):{ "hookSpecificOutput": { "hookEventName": "PostToolUse", "updatedToolOutput": "<去除 ANSI 转义码的纯文本输出>" } }
环境变量
| 变量 | 可用范围 | 说明 |
|---|---|---|
CLAUDE_PROJECT_DIR |
所有 hooks | 项目根目录的绝对路径 |
CLAUDE_ENV_FILE |
SessionStart、CwdChanged、FileChanged | 持久化环境变量的文件路径 |
CLAUDE_CODE_REMOTE |
所有 hooks | 远程环境中为 "true" |
${CLAUDE_PLUGIN_ROOT} |
插件 hooks | 插件目录路径 |
${CLAUDE_PLUGIN_DATA} |
插件 hooks | 插件数据目录路径 |
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS |
SessionEnd hooks | SessionEnd hooks 的可配置超时(毫秒,覆盖默认值) |
CLAUDE_CODE_SESSION_ID |
Bash 工具子进程(v2.1.132+) | 会话 UUID;与 hook 输入 JSON 中的 session_id 字段匹配。用于关联 bash 日志与 hook 遥测。 |
CLAUDE_EFFORT |
Bash 工具子进程(v2.1.133+) | 当前 effort 级别(low/medium/high/xhigh/max);与 hook 输入 JSON 中的 effort.level 匹配。 |
基于提示词的 Hooks
对于 Stop 和 SubagentStop 事件,你可以使用基于 LLM 的评估:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "审查所有任务是否完成。返回你的决策。",
"timeout": 30
}
]
}
]
}
}LLM 响应结构:
{
"decision": "approve",
"reason": "所有任务已成功完成",
"continue": false,
"stopReason": "任务完成"
}示例
示例 1:Bash 命令校验器(PreToolUse)
文件: .claude/hooks/validate-bash.py
#!/usr/bin/env python3
import json
import sys
import re
BLOCKED_PATTERNS = [
(r"\brm\s+-rf\s+/", "阻止危险的 rm -rf / 命令"),
(r"\bsudo\s+rm", "阻止 sudo rm 命令"),
]
def main():
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name", "")
if tool_name != "Bash":
sys.exit(0)
command = input_data.get("tool_input", {}).get("command", "")
for pattern, message in BLOCKED_PATTERNS:
if re.search(pattern, command):
print(message, file=sys.stderr)
sys.exit(2) # 退出码 2 = 阻塞性错误
sys.exit(0)
if __name__ == "__main__":
main()配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\""
}
]
}
]
}
}示例 2:安全扫描器(PostToolUse)
文件: .claude/hooks/security-scan.py
#!/usr/bin/env python3
import json
import sys
import re
SECRET_PATTERNS = [
(r"password\s*=\s*['\"][^'\"]+['\"]", "潜在的硬编码密码"),
(r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "潜在的硬编码 API 密钥"),
]
def main():
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name", "")
if tool_name not in ["Write", "Edit"]:
sys.exit(0)
tool_input = input_data.get("tool_input", {})
content = tool_input.get("content", "") or tool_input.get("new_string", "")
file_path = tool_input.get("file_path", "")
warnings = []
for pattern, message in SECRET_PATTERNS:
if re.search(pattern, content, re.IGNORECASE):
warnings.append(message)
if warnings:
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": f"{file_path} 的安全警告: " + "; ".join(warnings)
}
}
print(json.dumps(output))
sys.exit(0)
if __name__ == "__main__":
main()示例 3:自动格式化代码(PostToolUse)
文件: .claude/hooks/format-code.sh
#!/bin/bash
# 从 stdin 读取 JSON
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))")
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input', {}).get('file_path', ''))")
if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
exit 0
fi
# 根据文件扩展名格式化
case "$FILE_PATH" in
*.js|*.jsx|*.ts|*.tsx|*.json)
command -v prettier &>/dev/null && prettier --write "$FILE_PATH" 2>/dev/null
;;
*.py)
command -v black &>/dev/null && black "$FILE_PATH" 2>/dev/null
;;
*.go)
command -v gofmt &>/dev/null && gofmt -w "$FILE_PATH" 2>/dev/null
;;
esac
exit 0示例 4:提示词校验器(UserPromptSubmit)
文件: .claude/hooks/validate-prompt.py
#!/usr/bin/env python3
import json
import sys
import re
BLOCKED_PATTERNS = [
(r"delete\s+(all\s+)?database", "危险操作: 删除数据库"),
(r"rm\s+-rf\s+/", "危险操作: 删除根目录"),
]
def main():
input_data = json.load(sys.stdin)
prompt = input_data.get("user_prompt", "") or input_data.get("prompt", "")
for pattern, message in BLOCKED_PATTERNS:
if re.search(pattern, prompt, re.IGNORECASE):
output = {
"decision": "block",
"reason": f"已阻止: {message}"
}
print(json.dumps(output))
sys.exit(0)
sys.exit(0)
if __name__ == "__main__":
main()示例 5:智能 Stop Hook(基于提示词)
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "审查 Claude 是否完成了所有请求的任务。检查: 1) 是否所有文件都已创建/修改? 2) 是否有未解决的错误? 如未完成,说明缺少什么。",
"timeout": 30
}
]
}
]
}
}示例 6:上下文用量跟踪器(Hook 配对)
使用 UserPromptSubmit(消息前)和 Stop(响应后)hook 配合,跟踪每次请求的 token 消耗。
文件: .claude/hooks/context-tracker.py
#!/usr/bin/env python3
"""
上下文用量跟踪器 — 跟踪每次请求的 token 消耗。
使用 UserPromptSubmit 作为"消息前"hook,Stop 作为"响应后"hook,
计算每次请求的 token 使用量变化。
Token 计数方法:
1. 字符估算(默认):约 4 字符/token,无依赖
2. tiktoken(可选):更准确(约 90-95%),需要: pip install tiktoken
"""
import json
import os
import sys
import tempfile
# 配置
CONTEXT_LIMIT = 128000 # Claude 的上下文窗口(根据模型调整)
USE_TIKTOKEN = False # 如已安装 tiktoken 设为 True 以获得更高准确度
def get_state_file(session_id: str) -> str:
"""获取存储消息前 token 计数的临时文件路径,按会话隔离。"""
return os.path.join(tempfile.gettempdir(), f"claude-context-{session_id}.json")
def count_tokens(text: str) -> int:
"""统计文本中的 token 数。"""
if USE_TIKTOKEN:
try:
import tiktoken
enc = tiktoken.get_encoding("p50k_base")
return len(enc.encode(text))
except ImportError:
pass
# 基于字符的估算:英文约 4 字符/token
return len(text) // 4
def read_transcript(transcript_path: str) -> str:
"""读取并拼接 transcript 文件中的所有内容。"""
if not transcript_path or not os.path.exists(transcript_path):
return ""
content = []
with open(transcript_path, "r") as f:
for line in f:
try:
entry = json.loads(line.strip())
if "message" in entry:
msg = entry["message"]
if isinstance(msg.get("content"), str):
content.append(msg["content"])
elif isinstance(msg.get("content"), list):
for block in msg["content"]:
if isinstance(block, dict) and block.get("type") == "text":
content.append(block.get("text", ""))
except json.JSONDecodeError:
continue
return "\n".join(content)
def handle_user_prompt_submit(data: dict) -> None:
"""消息前 hook: 在请求前保存当前 token 计数。"""
session_id = data.get("session_id", "unknown")
transcript_path = data.get("transcript_path", "")
transcript_content = read_transcript(transcript_path)
current_tokens = count_tokens(transcript_content)
state_file = get_state_file(session_id)
with open(state_file, "w") as f:
json.dump({"pre_tokens": current_tokens}, f)
def handle_stop(data: dict) -> None:
"""响应后 hook: 计算并报告 token 变化。"""
session_id = data.get("session_id", "unknown")
transcript_path = data.get("transcript_path", "")
transcript_content = read_transcript(transcript_path)
current_tokens = count_tokens(transcript_content)
state_file = get_state_file(session_id)
pre_tokens = 0
if os.path.exists(state_file):
try:
with open(state_file, "r") as f:
state = json.load(f)
pre_tokens = state.get("pre_tokens", 0)
except (json.JSONDecodeError, IOError):
pass
delta_tokens = current_tokens - pre_tokens
remaining = CONTEXT_LIMIT - current_tokens
percentage = (current_tokens / CONTEXT_LIMIT) * 100
method = "tiktoken" if USE_TIKTOKEN else "estimated"
print(f"上下文 ({method}): ~{current_tokens:,} tokens ({percentage:.1f}% 已用, ~{remaining:,} 剩余)", file=sys.stderr)
if delta_tokens > 0:
print(f"本次请求: ~{delta_tokens:,} tokens", file=sys.stderr)
def main():
data = json.load(sys.stdin)
event = data.get("hook_event_name", "")
if event == "UserPromptSubmit":
handle_user_prompt_submit(data)
elif event == "Stop":
handle_stop(data)
sys.exit(0)
if __name__ == "__main__":
main()配置:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-tracker.py\""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-tracker.py\""
}
]
}
]
}
}工作原理:
UserPromptSubmit在你的提示词处理前触发 — 保存当前 token 计数Stop在 Claude 响应后触发 — 计算变化量并报告用量- 每个会话通过临时文件名中的
session_id隔离
Token 计数方法:
| 方法 | 准确度 | 依赖 | 速度 |
|---|---|---|---|
| 字符估算 | 约 80-90% | 无 | <1ms |
| tiktoken (p50k_base) | 约 90-95% | pip install tiktoken |
<10ms |
注意: Anthropic 尚未发布官方离线 tokenizer。两种方法都是近似值。transcript 包含用户提示词、Claude 的回复和工具输出,但不包含系统提示词或内部上下文。
示例 7:种子自动模式权限(一次性设置脚本)
一次性设置脚本,向 ~/.claude/settings.json 添加约 67 条安全权限规则,等效于 Claude Code 自动模式基线 — 无需 hook,无需记住未来选择。运行一次即可;可安全重复运行(跳过已存在的规则)。
文件: 10-advanced-features/setup-auto-mode-permissions.py
# 预览将要添加的内容
python3 10-advanced-features/setup-auto-mode-permissions.py --dry-run
# 应用
python3 10-advanced-features/setup-auto-mode-permissions.py添加的内容:
| 类别 | 示例 |
|---|---|
| 内置工具 | Read(*)、Edit(*)、Write(*)、Glob(*)、Grep(*)、Agent(*)、WebSearch(*) |
| Git 只读 | Bash(git status:*)、Bash(git log:*)、Bash(git diff:*) |
| Git 写入(本地) | Bash(git add:*)、Bash(git commit:*)、Bash(git checkout:*) |
| 包管理器 | Bash(npm install:*)、Bash(pip install:*)、Bash(cargo build:*) |
| 构建与测试 | Bash(make:*)、Bash(pytest:*)、Bash(go test:*) |
| 常用 shell | Bash(ls:*)、Bash(cat:*)、Bash(find:*)、Bash(cp:*)、Bash(mv:*) |
| GitHub CLI | Bash(gh pr view:*)、Bash(gh pr create:*)、Bash(gh issue list:*) |
故意排除的内容(此脚本永远不会添加):
rm -rf、sudo、force push、git reset --hardDROP TABLE、kubectl delete、terraform destroynpm publish、curl | bash、生产环境部署
示例 8:学习进度记录器(SessionEnd)
在每个 Claude Code 会话结束时记录你学习了哪些模块。进度存储在 ~/.claude-howto-progress.json 中 — 位于仓库之外,所以 git pull 不会覆盖你的数据。
为什么用 SessionEnd 而不是 Stop?
Stop 在每次 Claude 回复后都会触发。SessionEnd 在会话终止时触发一次 — 正是你想要的会话结束日记条目。
为什么用 /dev/tty 获取输入?
Hook 脚本通过 stdin 接收 JSON 载荷,所以交互式 read 必须使用 /dev/tty 直接访问终端。
文件: 08-hooks/session-end.sh
#!/usr/bin/env bash
# SessionEnd hook: 提示输入学习的模块,然后将会话记录追加到
# ~/.claude-howto-progress.json,实现持久化学习进度跟踪。
PROGRESS_FILE="$HOME/.claude-howto-progress.json"
# 防护: 只在此仓库内运行
if [[ "$CLAUDE_PROJECT_DIR" != *"claude-howto"* ]] && [[ "$PWD" != *"claude-howto"* ]]; then
exit 0
fi
if [ ! -f "$PROGRESS_FILE" ]; then
echo '{"sessions":[]}' > "$PROGRESS_FILE"
fi
DATE=$(date +"%Y-%m-%d")
TIME=$(date +"%H:%M")
echo ""
echo " 你学习了哪些模块?(例如 06,07 或按 Enter 跳过)"
echo " 01=Slash 02=Memory 03=Skills 04=Subagents 05=MCP"
echo " 06=Hooks 07=Plugins 08=Checkpoints 09=Advanced 10=CLI"
printf " > "
read -r INPUT </dev/tty
if [ -z "$INPUT" ] || [ "$INPUT" = "skip" ]; then
exit 0
fi
MODULES_JSON=$(echo "$INPUT" | tr ',' '\n' | tr -d ' ' | while read -r m; do
case "$m" in
01) echo '"01-slash-commands"' ;;
02) echo '"02-memory"' ;;
03) echo '"03-skills"' ;;
04) echo '"04-subagents"' ;;
05) echo '"05-mcp"' ;;
06) echo '"06-hooks"' ;;
07) echo '"07-plugins"' ;;
08) echo '"08-checkpoints"' ;;
09) echo '"09-advanced-features"' ;;
10) echo '"10-cli"' ;;
*) echo "\"$m\"" ;;
esac
done | paste -sd ',' -)
printf " 备注?(可选,按 Enter 跳过): "
read -r NOTES </dev/tty
# 将 NOTES 作为单独参数传递,让 Python 处理 JSON 转义 —
# 避免笔记包含引号或反斜杠时 JSON 格式错误。
python3 - "$PROGRESS_FILE" "$DATE" "$TIME" "$MODULES_JSON" "$NOTES" <<'PYEOF'
import sys, json
path, date, time_str, modules_raw, notes = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
new_session = {
"date": date,
"time": time_str,
"modules": json.loads(f"[{modules_raw}]") if modules_raw else [],
"notes": notes,
}
with open(path, 'r') as f:
data = json.load(f)
data.setdefault('sessions', []).append(new_session)
with open(path, 'w') as f:
json.dump(data, f, indent=2)
PYEOF
echo " 已保存到 $PROGRESS_FILE"安装 — 将脚本复制到项目的 hook 目录,使 settings.json 中的路径可以解析:
mkdir -p .claude/hooks
cp 08-hooks/session-end.sh .claude/hooks/
chmod +x .claude/hooks/session-end.sh配置(在 .claude/settings.json 中):
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-end.sh\""
}
]
}
]
}
}输出 — ~/.claude-howto-progress.json:
{
"sessions": [
{
"date": "2026-04-18",
"time": "14:32",
"modules": ["06-hooks", "07-plugins"],
"notes": "安装了第一个 hook,尝试了 pre-commit 示例"
}
]
}演示的关键模式:
| 模式 | 重要性 |
|---|---|
SessionEnd 事件 |
退出时触发一次 — 不像 Stop 那样每次响应后都触发 |
read -r INPUT </dev/tty |
Hook 拥有 stdin(JSON 载荷);用 /dev/tty 获取用户输入 |
$CLAUDE_PROJECT_DIR |
可移植路径 — 永远不要硬编码 /Users/yourname/... |
| 顶部的防护条件 | 防止 hook 在不相关的项目中运行(如全局安装时) |
| 存储在仓库外 | ~/ 路径在 git pull 后不会被覆盖 |
配套工具:可视化进度跟踪器
如需完整的复选框界面覆盖所有 10 个模块,在浏览器中打开配套的跟踪器:
open local-progress/index.html进度存储在浏览器 localStorage 中(永远不会写入仓库内的磁盘)。使用 Export 按钮将快照保存为 JSON,使用 Import 恢复。
插件 Hooks
插件可以在 hooks/hooks.json 文件中包含 hooks:
文件: plugins/hooks/hooks.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"
}
]
}
]
}
}插件 Hooks 中的环境变量:
${CLAUDE_PLUGIN_ROOT}— 插件目录路径${CLAUDE_PLUGIN_DATA}— 插件数据目录路径
这允许插件包含自定义的校验和自动化 hooks。
MCP 工具 Hooks
MCP 工具遵循 mcp__<server>__<tool> 模式:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__memory__.*",
"hooks": [
{
"type": "command",
"command": "echo '{\"systemMessage\": \"Memory 操作已记录\"}'"
}
]
}
]
}
}安全注意事项
免责声明
风险自负:Hooks 执行任意 shell 命令。你需对以下内容承担全部责任:
- 你配置的命令
- 文件访问/修改权限
- 潜在的数据丢失或系统损坏
- 在生产环境使用前在安全环境中测试 hooks
安全说明
- 需要工作区信任:
statusLine和fileSuggestionhook 输出命令现在需要接受工作区信任才能生效。 - HTTP hooks 和环境变量: HTTP hooks 需要显式
allowedEnvVars列表才能在 URL 中使用环境变量插值,防止敏感环境变量意外泄露到远程端点。 - 托管设置层级:
disableAllHooks设置现在遵循托管设置层级,意味着组织级设置可以强制禁用 hook,单个用户无法覆盖。 - PowerShell 自动批准(v2.1.119): PowerShell 工具命令现在可以在权限模式下自动批准,与 Bash 保持一致。为使用 PowerShell 作为 shell 工具的 Windows 用户提供了同等支持。
最佳实践
| 推荐 | 避免 |
|---|---|
| 校验和清理所有输入 | 盲目信任输入数据 |
给 shell 变量加引号: "$VAR" |
使用未加引号的: $VAR |
阻止路径遍历(..) |
允许任意路径 |
使用 $CLAUDE_PROJECT_DIR 的绝对路径 |
硬编码路径 |
跳过敏感文件(.env、.git/、密钥) |
处理所有文件 |
| 先在隔离环境中测试 hooks | 部署未经测试的 hooks |
HTTP hooks 使用显式 allowedEnvVars |
向 webhook 暴露所有环境变量 |
调试
启用调试模式
使用调试标志运行 Claude 以获取详细的 hook 日志:
claude --debugVerbose 模式
在 Claude Code 中使用 Ctrl+O 启用 verbose 模式,查看 hook 执行进度。
独立测试 Hooks
# 使用示例 JSON 输入测试
echo '{"tool_name": "Bash", "tool_input": {"command": "ls -la"}}' | python3 .claude/hooks/validate-bash.py
# 检查退出码
echo $?完整配置示例
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\"",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh\"",
"timeout": 30
},
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py\"",
"timeout": 10
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py\""
}
]
}
],
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-init.sh\""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "验证所有任务在停止前已完成。",
"timeout": 30
}
]
}
]
}
}Hook 执行细节
| 方面 | 行为 |
|---|---|
| 超时 | 默认 60 秒,每个命令可配置 |
| 并行化 | 所有匹配的 hooks 并行运行 |
| 去重 | 相同的 hook 命令会被去重 |
| 环境 | 在当前目录运行,使用 Claude Code 的环境 |
故障排查
Hook 未执行
- 检查 JSON 配置语法是否正确
- 检查 matcher 模式是否匹配工具名
- 确认脚本存在且可执行:
chmod +x script.sh - 运行
claude --debug查看 hook 执行日志 - 确认 hook 从 stdin 读取 JSON(不是命令参数)
Hook 意外阻止操作
- 使用示例 JSON 测试 hook:
echo '{"tool_name": "Write", ...}' | ./hook.py - 检查退出码: 0 为允许,2 为阻止
- 检查 stderr 输出(退出码 2 时显示)
JSON 解析错误
- 始终从 stdin 读取,不要从命令参数读取
- 使用正确的 JSON 解析(不要用字符串处理)
- 优雅地处理缺失字段
安装
第一步:创建 Hooks 目录
mkdir -p ~/.claude/hooks第二步:复制示例 Hooks
cp 08-hooks/*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/*.sh第三步:在 Settings 中配置
编辑 ~/.claude/settings.json 或 .claude/settings.json,添加上述 hook 配置。
相关概念
- Checkpoints 与 Rewind — 保存和恢复会话状态
- Slash Commands — 创建自定义斜杠命令
- Skills — 可复用的自主能力
- Subagents — 委派任务执行
- Plugins — 打包的扩展包
- 高级功能 — 探索 Claude Code 高级能力
更多资源
- Memory 指南 — 持久化上下文配置
- 官方 Hooks 文档 — 完整的 hooks 参考
- CLI 参考 — 命令行接口文档
最后更新: 2026 年 5 月 9 日 Claude Code 版本: 2.1.138 来源:
- https://code.claude.com/docs/en/hooks
- https://code.claude.com/docs/en/changelog
- https://github.com/anthropics/claude-code/releases/tag/v2.1.118
- https://github.com/anthropics/claude-code/releases/tag/v2.1.131
- https://github.com/anthropics/claude-code/releases/tag/v2.1.138 兼容模型: Claude Sonnet 4.6、Claude Opus 4.7、Claude Haiku 4.5