Skip to content

Hooks 参考

Hooks 是在 Claude Code 事件发生时自动执行的脚本,用于自动化、校验、权限管理和自定义工作流。

概览

Hooks 是在 Claude Code 特定事件发生时自动执行的动作(shell 命令、HTTP webhook、LLM 提示词、MCP 工具调用或 subagent 评估)。它们通过 JSON 输入接收数据,通过退出码和 JSON 输出返回结果。

核心特性:

  • 事件驱动的自动化
  • 基于 JSON 的输入/输出
  • 支持 commandhttpmcp_toolpromptagent 五种 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 评估的提示词。主要用于 StopSubagentStop 事件,实现智能任务完成检查。

{
  "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): PostToolUsePostToolUseFailure hook 输入现在包含 duration_ms — 详见 PostToolUse 部分。

PreToolUse

在 Claude 创建工具参数之后、处理之前运行。用于校验或修改工具输入。

配置:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
          }
        ]
      }
    ]
  }
}

常用 matcher: TaskBashGlobGrepReadEditWriteWebFetchWebSearch

输出控制:

  • 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 执行的时间。PostToolUsePostToolUseFailure 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)时运行。支持基于提示词的评估,实现智能任务完成检查。

额外输入字段: StopSubagentStop 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: startupresumeclearcompact

特殊功能: 使用 CLAUDE_ENV_FILE 持久化环境变量(在 CwdChangedFileChanged hook 中也可用):

#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
fi
exit 0

SessionEnd

在会话结束时运行,用于清理或最终日志记录。无法阻止终止。

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 支持的事件: PreToolUsePostToolUseStop

这允许在使用 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 级别:lowmediumhighxhighmax

退出码

退出码 含义 行为
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 工具。BashEditRead 等工具的 PostToolUse hook 可以在 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

对于 StopSubagentStop 事件,你可以使用基于 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\""
          }
        ]
      }
    ]
  }
}

工作原理:

  1. UserPromptSubmit 在你的提示词处理前触发 — 保存当前 token 计数
  2. Stop 在 Claude 响应后触发 — 计算变化量并报告用量
  3. 每个会话通过临时文件名中的 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 -rfsudo、force push、git reset --hard
  • DROP TABLEkubectl deleteterraform destroy
  • npm publishcurl | 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

安全说明

  • 需要工作区信任: statusLinefileSuggestion hook 输出命令现在需要接受工作区信任才能生效。
  • 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 --debug

Verbose 模式

在 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 配置。

相关概念

更多资源


最后更新: 2026 年 5 月 9 日 Claude Code 版本: 2.1.138 来源: