Skip to content

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 和 PostToolUseFailure hook 输入现在包含 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 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 支持的事件: 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 等工具的 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

对于 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\""
          }
        ]
      }
    ]
  }
}

工作原理:

  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 -rf、sudo、force push、git reset --hard
  • DROP TABLE、kubectl delete、terraform destroy
  • npm 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 和 fileSuggestion 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 来源: