关于 hooks-create
hooks-create 是一项 Claude Code 技能,可将需求转化为生命周期钩子、脚本和设置配置。它指导选择事件、匹配范围、实现与验证,并明确钩子是否应阻止操作。
主要功能
- 01将需求映射到生命周期事件
- 02选择事件匹配器和阻止行为
- 03编写并配置钩子脚本
- 04合并钩子设置而不覆盖现有项
- 05测试允许与阻止行为
适用场景
- 01阻止访问指定文件
- 02在 Agent 完成前运行检查
- 03在会话开始时注入上下文
- 04对工具活动执行格式化或记录
MCP 相关
该技能将钩子安全注意事项与 MCP 服务的安全提醒相提并论,但未说明 MCP 连接或服务管理功能。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ coleam00/ skills --skill hooks-create安装后在 Claude Code 中输入/hooks-create确认已加载。
详细介绍
创建 Hook:将想法变成可用的 Claude Code Hook
用户希望 Hook 执行的操作:$ARGUMENTS
如果已填写,请将其视为行为规范,并以此为起点。不要再次询问用户已经说明的内容;只需确认尚不明确的部分(确切路径 / 命令 / 模式,以及是否必须阻止)。如果为空,请先询问 Hook 应保证什么或执行什么操作(工作流第 1 步)。
什么是 Hook(30 秒简介)
Hook 是一种确定性代码,会在 Claude Code 生命周期事件发生时自动触发——例如工具运行前、工具运行后、Agent 尝试结束时、会话开始时等。与技能或子 Agent 不同,Agent 无法自行选择是否调用 Hook——无论模型是否“记得”,Hook 都会触发。这正是它的意义:规则只是要求 Agent 遵循行为;而 Hook 会在模型无法绕过的工具层面保证行为得到执行。
用户提出想法(“Agent 绝不能读取我的 .env”);此技能负责编写代码并完成接入。用户不需要懂 Python。
一切都是组合——Hook 只是一个小脚本,由运行框架在指定时机执行,并在 .claude/settings.json 中配置。你正在 AI 层添加另一项确定性保障。
最重要的一点:选对事件,并确认它能否阻止操作
用户想要的行为对应一个生命周期事件。根据触发时机和是否必须停止某项操作来选择:
| 用户想要…… | 事件 | 能否阻止? |
|---|---|---|
| 阻止 Agent 执行某项操作(读取机密、编辑受保护路径、运行破坏性命令) | PreToolUse ⭐ | 可以——在工具运行前阻止它 |
| 在操作后做出响应(自动格式化已编辑的文件、记录命令、注入上下文) | PostToolUse | 不可以——工具已经运行;只能观察 / 格式化 / 注入 |
| 保证检查通过后才能“完成”工作(测试 / lint / 类型检查通过后才能停止) | Stop(或 SubagentStop) | 可以——阻止停止,并让 Agent 返回工作状态 |
| 在模型看到用户提示词之前对其进行检查或门控 | UserPromptSubmit | 可以——阻止提示词;也可以注入上下文 |
| 每次会话开始时加载上下文 | SessionStart | 不可以——只能注入上下文 |
| 在 Agent 需要你协助 / 完成时收到通知 | Notification(或 Stop) | 不可以——只能执行副作用(桌面通知 / Slack / 声音) |
| 在上下文压缩前快照保存状态 | PreCompact | 不可以 |
Pre = 保证 / 门控。Post = 响应 / 记录。 如果用户的目标是“确保 X 绝不会发生”或“直到 Y 才能完成”,这就是一个阻止型 Hook(PreToolUse / Stop / UserPromptSubmit)。如果目标是“在 W 发生时执行 Z”,这就是一个观察 / 响应型 Hook(PostToolUse / SessionStart / Notification)。
必读内容(先执行此步骤)
Hook 事件列表和精确的 stdin/stdout 约定会不断变化——不要依赖旧版本快照。编写之前,获取当前文档,确认事件名称、输入字段和控制协议:
- Hook 参考:https://code.claude.com/docs/en/hooks
- Hook 指南(示例):https://code.claude.com/docs/en/hooks-guide
对以上页面使用 WebFetch,并对照即将编写的内容进行核实。如果获取失败,请基于上表中的规范事件继续,并在报告中说明这一点,以便用户再次确认。
执行协议(钩子如何与 Claude Code 通信)
- 输入: Claude Code 会通过 stdin 传入一个 JSON 对象,其中始终包含
session_id、cwd、hook_event_name;还会包含与事件相关的字段,例如tool_name+tool_input(工具事件)、prompt(UserPromptSubmit)、source(SessionStart)。 - 输出 / 控制:
exit 0- 允许 / 成功。对于UserPromptSubmit和SessionStart,打印到 stdout 的任何内容都会 注入 Agent 的上下文。exit 2- 阻止。 操作会被拦截,打印到 stderr 的内容会作为原因反馈给 Agent,使其调整行为。 (只有支持阻止的事件才会遵循此设置 - 请参阅表格。)- 任何其他退出码 - 非阻止性错误;会显示给用户,但执行会继续。
- 高级选项(可选): 不使用退出码,而是在 stdout 上打印一个 JSON 对象 - 例如
{"decision":"block", "reason":"…"}(Stop),或{"hookSpecificOutput":{"hookEventName":"PreToolUse", "permissionDecision":"deny","permissionDecisionReason":"…"}}。除非用户需要修改输入/输出,或通过additionalContext注入上下文,否则优先使用简单的退出码形式。请对照获取的文档确认字段名称。
工作流
1. 理解需求(从 $ARGUMENTS 开始;仅在信息缺失时询问)
先从用户在 $ARGUMENTS 中已有的描述开始(用户可能并不熟悉技术)。用通俗语言明确两点,只询问缺失的信息:
- 要执行或阻止什么,以及何时执行(操作前/后、结束时、会话开始时)?
- 匹配要多精确?(“任何
.env文件”、“migrations/文件夹”、“rm -rf”、“我的测试命令以非零状态退出”。)获取具体的文件路径 / 命令 / 模式 - 保证效果取决于匹配范围,因此不要猜测。如果需求含糊,提出一个具体解释并请用户确认。
2. 阅读文档
获取钩子参考文档 / 指南(见上文),并确认目标事件的名称、stdin 字段和控制协议。
3. 选择事件(和匹配器)
使用表格选择一个事件。选择能严格限定范围的匹配器 - 对于工具事件,使用工具名称(例如 "Bash"、"Edit|Write"、
"mcp__.*");空值 / "*" 表示每次出现都匹配。如果目标明确,不要对所有情况都触发。
4. 编写钩子脚本
- 默认使用
uv单文件 Python 脚本,路径为.claude/hooks/<event_snake_case>.py- 使用uv run --scriptshebang 和内联# /// script元数据,这样无需安装,也不需要单独的项目 venv。只有用户提出要求时才使用其他语言。 - 从 stdin 读取 JSON,执行检查,并:
- 要阻止:向
stderr打印清楚的原因,并执行sys.exit(2); - 要允许:执行
sys.exit(0)(对于会注入上下文的事件,可选择向 stdout 打印上下文)。
- 要阻止:向
- 失败时放行。 将主体代码包在错误处理逻辑中,确保任何意外错误都会以
0退出(损坏的钩子绝不能让用户的 会话无法继续)。唯一有意使用的非零退出是主动阻止时的exit 2。 - 保持精简、易读 - 用户之后可能需要调整匹配的路径 / 命令。
- 如果该事件已有钩子,扩展而不是覆盖(添加你的检查,并保留原有逻辑)。
- 仅适用于
Stop/SubagentStop: 首先检查stop_hook_active;如果为 true,则执行sys.exit(0)。没有 此保护时,钩子会阻止停止,Agent 继续工作并再次尝试停止,又会再次被阻止 - 从而陷入循环。
⚠️ 从钩子中运行项目命令(编写钩子前请阅读)
钩子本身在**
uv run启动的隔离临时环境中**运行。它使用的解释器不是项目的解释器,也没有项目依赖。因此:
- 绝不要用
sys.executable或["python", "-m", "pytest", ...]这样的裸列表重建命令。这样运行的是钩子用的临时 Python,它没有 pytest、没有项目包,什么都没有。命令每次都会因No module named …而失败——因此,“测试通过前不要结束”的钩子,无论测试通过还是失败都会拦截,并给出荒谬的原因。看起来好像能用,其实不能。- 务必在项目目录中,将用户的命令原样作为 shell 字符串运行——移除 uv 的临时 venv,并将项目自己的 venv 放在最前面。这两个步骤缺一不可。仅设置
shell=True不够:uv run会把它的临时解释器放在PATH最前面,并设置VIRTUAL_ENV。但移除 uv 的 venv 并不会激活项目的 venv——钩子不是用户的 shell,因此.venv一开始就不在PATH中,python会退回到机器上的全局解释器。全局解释器的包集不同(而且往往有问题),所以即使测试套件完全通过,钩子也会以状态码 2 退出,并归咎于无关模块。请原样复制此辅助函数:将命令放在文件顶部一个单独命名的常量中,让用户能轻松找到并编辑这一行。TEST_COMMAND = "python -m pytest -q" # 用户输入的原始命令;用户只需编辑这一行 def _project_env(project_root: Path) -> dict: """移除 uv 的临时 venv,并将项目自己的 venv 放在最前面的 os.environ。""" env = os.environ.copy() ephemeral = env.pop("VIRTUAL_ENV", None) parts = env.get("PATH", "").split(os.pathsep) if ephemeral: # 1. 移除 uv 的临时 venv drop = {os.path.join(ephemeral, "Scripts"), os.path.join(ephemeral, "bin")} parts = [p for p in parts if p not in drop] for candidate in (".venv", "venv"): # 2. 将项目自己的 venv 放在最前面 for bindir in ("Scripts", "bin"): venv_bin = project_root / candidate / bindir if venv_bin.is_dir(): env["VIRTUAL_ENV"] = str(project_root / candidate) parts.insert(0, str(venv_bin)) env["PATH"] = os.pathsep.join(parts) return env env["PATH"] = os.pathsep.join(parts) return env root = Path(hook_input["cwd"]) # Claude Code 传入的项目根目录 result = subprocess.run( TEST_COMMAND, shell=True, capture_output=True, text=True, cwd=str(root), env=_project_env(root), )- 如果命令必须从子目录运行(例如,monorepo,或测试配置位于更深层目录的项目,比如
app/backend/),请询问用户并传入该目录:cwd=Path(hook_input["cwd"]) / "app/backend"。目录弄错会导致钩子始终拦截,用户会以为“钩子坏了”。
5. 将其接入 settings.json
编辑 .claude/settings.json(如果文件不存在,则创建)。将内容合并到任何现有的 hooks 块中——绝不要覆盖其他事件或同一事件上的其他钩子。结构如下:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Edit|Write|Read|Bash",
"hooks": [ { "type": "command", "command": "uv run .claude/hooks/pre_tool_use.py" } ] }
]
}
}
6. 亲自验证,然后解释并提醒
交付前先运行钩子。 通过 stdin 向钩子输入一个示例事件,并检查退出代码——不要交付一个你只读过、却从未运行过的钩子。一个总是阻止操作或从不阻止操作的钩子,在错误时机触发之前,看起来和正常工作的钩子没有区别。
# should ALLOW (exit 0)
echo '{"session_id":"t","cwd":"<project-root>","hook_event_name":"Stop","stop_hook_active":false}' | uv run .claude/hooks/stop.py; echo "exit=$?"
- 对于运行命令的钩子(测试/lint/类型检查),这是强制要求,而且必须检查两个方向:命令通过时退出代码为 0,命令确实失败时退出代码为 2。如果两种情况下都退出代码为 2,说明命令未能解析——重新阅读第 4 步中关于
sys.executable的警告。 - 对于阻止操作的保护钩子,输入一个应该被阻止的载荷,再输入一个应该通过的载荷。
- 如果检查结果不正确,先修复脚本并重新运行,再报告成功。
然后:
- 用通俗的话告诉用户你构建了什么:钩子对应哪个事件、它能保证什么,以及他们可以改哪一行来调整它。
- 告诉他们如何在 Agent 中验证:对于阻止操作的钩子,说明一个应该被阻止的操作(“让我读取 env 文件——观察它拒绝操作”);对于观察类钩子,说明输出会出现在哪里(日志或通知)。
- 报告你验证了什么,并明确说明无法验证的部分。
- 安全说明(务必说明): 钩子会在每次匹配事件发生时自动运行任意代码,并使用你的凭据。像审查 CI 配置一样审查钩子;只运行你信任的钩子。(与 MCP 服务的注意事项相同。)
质量检查
- ✅ 行为对应正确的事件,阻止操作的目标使用支持阻止操作的事件(PreToolUse/Stop/UserPromptSubmit),而不是 PostToolUse。
- ✅ 匹配器的范围符合用户的实际意图(不会意外地对所有内容触发)。
- ✅ 脚本采用失败时放行策略——任何错误都以 0 退出;只有在确实要阻止操作时才以
exit 2退出,并通过 stderr 给出清晰的原因。 - ✅ 任何项目命令都通过
shell=True在项目的cwd中原样运行,从PATH中移除 uv 的临时 venv,并将项目自己的 venv 放在PATH首位——绝不能用sys.executable重新构造命令(钩子自己的解释器没有项目依赖,而全局解释器中的依赖又不正确)。 - ✅
Stop/SubagentStop钩子会遵守stop_hook_active,避免陷入循环。 - ✅
settings.json已与现有内容合并,而非覆盖;现有钩子仍然保留。 - ✅ 你已运行钩子,并确认它在应该放行时以 0 退出、在应该阻止时以 2 退出——而不只是读过它。
- ✅ 用户收到了通俗易懂的说明、测试方法和安全说明。
备注
- 钩子是 AI Layer 中确定性的基础层——将它用于不可妥协的要求(机密信息、受保护路径、“全部通过后才能结束”),而不是用于规则或技能足以处理的事项。
- 阻止操作的钩子能覆盖多大范围,取决于它的匹配器——它能保证钩子运行,但由你决定它会拦截什么。应如实告知用户其覆盖范围的边界(例如,匹配
.env无法捕获经过 base64 编码的读取操作)。 - 钩子要保持快速——每次匹配事件发生时都会运行。耗时较长的工作应交给异步钩子或技能。