用于构建Claude Code hooks的Python工具包
摘要
一个减少构建Claude Code hooks样板代码的Python工具包,提供类型安全的事件处理器,用于工具使用前/后、提示提交和会话开始钩子。
查看缓存全文
缓存时间: 2026/05/29 07:16
RasmusGodske/claude-hook-utils 来源:https://github.com/RasmusGodske/claude-hook-utils
claude-hook-utils
一个用于构建 Claude Code 钩子(https://docs.anthropic.com/en/docs/claude-code/hooks)的 Python 工具包,只需极少的样板代码。
什么是 Claude Code 钩子?
Claude Code 钩子是在 Claude Code 执行过程中的特定时机运行的自定义脚本。它们允许你:
- 验证工具调用在执行之前(PreToolUse)
- 响应工具调用执行后的结果(PostToolUse)
- 拦截用户提示在 Claude 看到之前(UserPromptSubmit)
- 初始化会话启动时的状态(SessionStart)
为什么需要这个包?
构建 Claude Code 钩子涉及重复的样板代码:
- 从 stdin 解析 JSON
- 验证输入结构
- 将响应格式化为正确的模式
- 优雅地处理错误
claude-hook-utils 处理了所有这些,让你专注于验证逻辑。
设计理念
- 单一模式 - 扩展
HookHandler,覆盖你需要的钩子 - 类型安全 - 输入使用带类型的 dataclass,响应使用构建器模式
- 显式控制 - 输入上的辅助方法,但由你决定何时跳过/允许/拒绝
- 多钩子支持 - 一个 Python 程序可以处理多种钩子类型
- 无繁重依赖 - 核心包依赖极少;可按需引入自己的 AI SDK
安装
pip install claude-hook-utils
快速开始
#!/usr/bin/env python3
"""验证 Data 类是否具有 TypeScript 注解。"""
from claude_hook_utils import HookHandler, PreToolUseInput, PreToolUseResponse
class DataClassValidator(HookHandler):
def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
# 如果不是 Data 类文件则跳过
if not input.file_path_matches('**/app/Data/**/*.php'):
return None
# 检查必需注解
if input.content and '#[TypeScript()]' not in input.content:
return PreToolUseResponse.deny(
"Data 类必须包含 #[TypeScript()] 注解以便类型生成"
)
return PreToolUseResponse.allow()
if __name__ == "__main__":
DataClassValidator().run()
在 .claude/settings.json 中配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/data_class_validator.py"
}
]
}
]
}
}
支持的钩子类型
| 钩子类型 | 运行时机 | 使用场景 |
|---|---|---|
PreToolUse | 工具执行前 | 验证文件路径、检查内容、阻止危险操作 |
PostToolUse | 工具执行后 | 记录结果、触发后续操作、收集指标 |
UserPromptSubmit | 用户提交提示时 | 验证提示、添加上下文、执行策略 |
SessionStart | Claude Code 会话开始时 | 初始化状态、设置环境变量 |
API 参考
HookHandler 基类
扩展此类并覆盖你需要的钩子:
from claude_hook_utils import HookHandler
class MyHandler(HookHandler):
def __init__(self):
super().__init__()
# 在此添加共享状态
self._cache: dict = {}
def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
"""在工具执行前调用。返回 None 以跳过。"""
return None
def post_tool_use(self, input: PostToolUseInput) -> PostToolUseResponse | None:
"""在工具执行后调用。返回 None 以跳过。"""
return None
def user_prompt_submit(self, input: UserPromptSubmitInput) -> UserPromptSubmitResponse | None:
"""当用户提交提示时调用。返回 None 以跳过。"""
return None
def session_start(self, input: SessionStartInput) -> SessionStartResponse | None:
"""当会话启动时调用。返回 None 以跳过。"""
return None
if __name__ == "__main__":
MyHandler().run()
PreToolUseInput
PreToolUse 钩子的输入:
@dataclass
class PreToolUseInput:
# 通用字段
session_id: str
cwd: str
hook_event_name: str # 始终为 "PreToolUse"
# PreToolUse 特有的
tool_name: str # "Write", "Edit", "Bash" 等
tool_input: dict # 工具特定的参数
tool_use_id: str
# 辅助方法
def file_path_matches(self, *globs: str) -> bool:
"""检查 tool_input.file_path 是否匹配任何 glob 模式。"""
def file_path_excludes(self, *globs: str) -> bool:
"""检查 tool_input.file_path 是否**不**匹配任何 glob 模式。"""
# 便捷属性
@property
def file_path(self) -> str | None:
"""从 tool_input 获取 file_path(适用于 Write/Edit/Read 工具)。"""
@property
def content(self) -> str | None:
"""从 tool_input 获取 content(适用于 Write 工具)。"""
@property
def command(self) -> str | None:
"""从 tool_input 获取 command(适用于 Bash 工具)。"""
PreToolUseResponse
PreToolUse 钩子的响应构建器:
class PreToolUseResponse:
@staticmethod
def allow(reason: str | None = None) -> PreToolUseResponse:
"""允许工具执行。"""
@staticmethod
def deny(reason: str) -> PreToolUseResponse:
"""阻止工具。原因会作为反馈显示给 Claude。"""
@staticmethod
def ask(reason: str) -> PreToolUseResponse:
"""请求用户确认后再继续。"""
def with_updated_input(self, **updates) -> PreToolUseResponse:
"""在工具执行前修改 tool_input(仅在与 allow 一起使用时有效)。"""
HookLogger
基于 JSONL 的日志记录,便于调试。日志按命名空间(插件名称)组织。
from claude_hook_utils import HookHandler, HookLogger
class MyHandler(HookHandler):
def __init__(self):
# 日志写入 .claude/logs/my-plugin/hooks.jsonl
super().__init__(
logger=HookLogger.create_default("MyHandler", namespace="my-plugin")
)
def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
# 会话 ID 从输入中自动添加
self.logger.info("检查文件", file_path=input.file_path)
# ... 验证逻辑 ...
self.logger.decision("允许", reason="验证通过")
return PreToolUseResponse.allow()
日志格式(JSONL - 每行一个 JSON 对象):
{"ts": "2025-01-04T10:15:23.456+00:00", "level": "INFO", "hook": "MyHandler", "namespace": "my-plugin", "session": "abc123", "msg": "检查文件", "file_path": "/path/to/file.php"}
{"ts": "2025-01-04T10:15:23.458+00:00", "level": "DECISION", "hook": "MyHandler", "namespace": "my-plugin", "session": "abc123", "msg": "decision=allow", "decision": "allow", "reason": "验证通过"}
配置:
- 默认位置:
{cwd}/.claude/logs/{namespace}/hooks.jsonl - 无命名空间:
{cwd}/.claude/logs/hooks.jsonl - 通过环境变量
CLAUDE_HOOK_LOG_DIR覆盖日志目录 - 通过环境变量
CLAUDE_HOOK_LOG_NAMESPACE覆盖日志命名空间/子目录 - 会话 ID 自动从钩子输入中提取
示例
验证 Vue 组件结构
class VueValidator(HookHandler):
def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
if not input.file_path_matches('**/*.vue'):
return None
content = input.content or ''
# 检查标签顺序:<template> 在 <style> 之前
script_pos = content.find('<script')
template_pos = content.find('<template')
style_pos = content.find('<style')
if script_pos > template_pos or template_pos > style_pos:
return PreToolUseResponse.deny(
"Vue 组件必须按顺序包含标签:<template>, <script>, <style>"
)
# 检查是否使用了 setup lang="ts"
if '<script setup lang="ts">' not in content:
return PreToolUseResponse.deny(
'Vue 组件必须使用 <script setup lang="ts">'
)
return PreToolUseResponse.allow()
验证控制器位置
class ControllerValidator(HookHandler):
def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
if not input.file_path_matches('**/*Controller.php'):
return None
# 控制器必须位于 app/Http/Controllers/ 下
if not input.file_path_matches('**/app/Http/Controllers/**/*.php'):
return PreToolUseResponse.deny(
f"控制器必须位于 app/Http/Controllers/ 中。"
f"实际路径:{input.file_path}"
)
return PreToolUseResponse.allow()
禁止使用 FormRequest(推荐 Data 类)
class NoFormRequestValidator(HookHandler):
def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
if not input.file_path_matches('**/*Controller.php'):
return None
content = input.content or ''
if 'FormRequest' in content:
return PreToolUseResponse.deny(
"请勿使用 FormRequest 类。请改用 Data 类。"
"示例参见:app/Data/"
)
return PreToolUseResponse.allow()
多钩子处理器(Pre + Post)
class FileTracker(HookHandler):
def __init__(self):
super().__init__()
self._pending_writes: set[str] = set()
def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
if input.tool_name == 'Write' and input.file_path:
self._pending_writes.add(input.file_path)
self.logger.info(f"跟踪写入:{input.file_path}")
return PreToolUseResponse.allow()
def post_tool_use(self, input: PostToolUseInput) -> PostToolUseResponse | None:
if input.tool_name == 'Write' and input.file_path:
self._pending_writes.discard(input.file_path)
self.logger.info(f"写入完成:{input.file_path}")
return None
Claude Code 钩子响应格式
本包生成符合官方 hookSpecificOutput 格式的响应:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "你的原因在此"
}
}
决策选项
| 决策 | 效果 |
|---|---|
allow | 工具立即执行,原因显示给用户 |
deny | 工具被阻止,原因显示给 Claude(以便其调整) |
ask | 显示用户确认对话框 |
修改工具输入
使用 with_updated_input() 在工具执行前修改参数:
def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
# 自动修正一个常见错误
if input.file_path and '/data/' in input.file_path:
corrected = input.file_path.replace('/data/', '/Data/')
return PreToolUseResponse.allow("已自动修正路径").with_updated_input(
file_path=corrected
)
return PreToolUseResponse.allow()
错误处理
本包优雅地处理错误:
- 无效的 JSON 输入:返回退出码 0(无输出 = 允许)
- 未知的钩子类型:返回 None(跳过)
- 处理器中的异常:记录到 stderr,返回退出码 0(故障时开放)
这种“故障开放“方式确保即使出现错误,你的钩子也不会阻塞 Claude Code。
环境变量
Claude Code 向钩子提供以下环境变量:
| 变量 | 描述 |
|---|---|
CLAUDE_PROJECT_DIR | 项目根目录的绝对路径 |
CLAUDE_CODE_REMOTE | 如果在 Web 环境中运行则为 "true" |
本包使用:
| 变量 | 描述 |
|---|---|
CLAUDE_HOOK_LOG_DIR | 覆盖默认日志目录(默认:.claude/logs/{namespace}/) |
CLAUDE_HOOK_LOG_NAMESPACE | 覆盖日志命名空间/子目录 |
通过 input.cwd 或 os.environ 访问。
为新的钩子类型扩展支持
要添加对新钩子类型的支持:
- 在
inputs/中创建输入数据类 - 在
responses/中创建响应类 - 在
HookHandler中添加处理器方法 - 在
HookHandler._dispatch()中添加分发分支
参见现有实现以了解模式。
许可证
MIT
相似文章
@sairahul1: https://x.com/sairahul1/status/2069710540654645550
一份全面指南,解释Claude Code Hooks,这些是可编程的检查点,在操作前运行以强制执行规则并阻止或允许工具调用,比CLAUDE.md指令提供更可靠的控制。
我创建了一个免费的 Claude Code 工具包——包含 64 个技能、7 个智能体、16 条斜杠命令以及针对完整技术栈的自动格式化钩子
一个免费的、开源的 Claude Code 工具包,增加了 64 个技能、7 个自主智能体、16 条斜杠命令以及针对多种编程语言的自动格式化钩子。
@vincemask: 很多人不知道什么时候该用 Hooks。 我的判断很简单:凡是你需要反复提醒 Claude 的事,都应该考虑从 prompt 里拿出来,交给 Hook。 比如: 1、每次改完代码都要格式化 2、每次提交前都要跑 lint / test 3、…
文章介绍了在Claude AI编程辅助中,何时应该使用Hooks来固化重复性规则(如自动格式化、提交前检查等),建议将稳定、重复、易忘的流程从Prompt中剥离交给环境默认执行。
@DanKornas: 想理解Claude Code?研究框架,而不仅仅是提示词。claude-code-from-scratch是一个Python学习…
一个Python学习仓库,通过23个渐进式会话逆向工程Claude Code风格的智能体架构,涵盖规划、子智能体、上下文管理等。
anthropics/claude-code
Claude Code 是 Anthropic 推出的一款代理式编码工具,它运行在终端中,能够理解代码库,并通过自然语言命令帮助完成诸如执行常规编码任务、解释代码和处理 git 工作流等任务。