create-or-audit-hook

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Create or audit a hook

创建或审核钩子

Hooks are deterministic gates bash-scripted against Claude Code events. Use them for things that must run every time, fast.
钩子是针对Claude Code事件编写的确定性bash脚本门控机制,适用于必须每次快速运行的任务。

Before You Start

开始之前

  • skills/meta/create-or-audit-hook/templates/hook.sh
    — annotated blank hook with stdin parsing, dispatch, and exit-code examples.
  • skills/meta/create-or-audit-hook/lib/validate.sh
    — mechanical checks (shebang, pipefail, stdin read, stderr-when-blocking).
  • Event taxonomy:
    PreToolUse
    (block before tool runs),
    PostToolUse
    (act after tool runs),
    Stop
    (gate session end),
    SessionStart
    (warm caches). Exit codes:
    0
    allow,
    2
    block + stderr message.
  • skills/meta/create-or-audit-hook/templates/hook.sh
    — 带注释的空白钩子模板,包含标准输入解析、分发和退出代码示例。
  • skills/meta/create-or-audit-hook/lib/validate.sh
    — 机械检查脚本(检查shebang、pipefail、标准输入读取、阻塞时的标准错误输出)。
  • 事件分类:
    PreToolUse
    (工具运行前阻止)、
    PostToolUse
    (工具运行后执行操作)、
    Stop
    (会话结束门控)、
    SessionStart
    (预热缓存)。退出代码:
    0
    允许执行,
    2
    阻止并输出标准错误信息。

Mode 1 — build a new hook

模式1 — 构建新钩子

Step 1: pick the event

步骤1:选择事件

You want...EventMatcher
Block a write to protected files
PreToolUse
Write|Edit
Refuse a dangerous shell command
PreToolUse
Bash
Format a file after editing
PostToolUse
Write|Edit
Run typecheck before "done"
Stop
Warm caches at session start
SessionStart
If the rule isn't deterministic (requires judgment), it's not a hook — it's a skill.
你需要...事件匹配规则
阻止写入受保护文件
PreToolUse
Write|Edit
拒绝危险的Shell命令
PreToolUse
Bash
编辑后格式化文件
PostToolUse
Write|Edit
“完成”前运行类型检查
Stop
会话开始时预热缓存
SessionStart
如果规则不具备确定性(需要主观判断),则不属于钩子范畴,而是skill。

Step 2: copy the template

步骤2:复制模板

bash
cp .claude/skills/meta/create-or-audit-hook/templates/hook.sh .claude/hooks/{hook-name}.sh
chmod +x .claude/hooks/{hook-name}.sh
bash
cp .claude/skills/meta/create-or-audit-hook/templates/hook.sh .claude/hooks/{hook-name}.sh
chmod +x .claude/hooks/{hook-name}.sh

Step 3: fill in the logic

步骤3:填充逻辑

Enforce these rules:
  • #!/usr/bin/env bash
    +
    set -euo pipefail
    at the top.
  • Read JSON from stdin:
    input=$(cat)
    .
  • Short-circuit
    exit 0
    on empty input, wrong tool, or irrelevant file type.
  • If blocking (
    exit 2
    ), print an actionable message to stderr:
    echo "BLOCKED: reason + instead" >&2
    .
  • Check for tool presence with
    command -v
    before running an external tool. Never
    exit 1
    because a formatter is missing
    — the user's local state isn't your hook's concern.
遵循以下规则:
  • 文件顶部需包含
    #!/usr/bin/env bash
    +
    set -euo pipefail
  • 从标准输入读取JSON:
    input=$(cat)
  • 若输入为空、工具不匹配或文件类型不相关,直接
    exit 0
    短路退出。
  • 如果需要阻止操作(
    exit 2
    ),需向标准错误输出可执行提示信息:
    echo "BLOCKED: 原因 + 替代方案" >&2
  • 运行外部工具前,先用
    command -v
    检查工具是否存在。绝对不要因为格式化工具缺失就
    exit 1
    — 用户的本地环境状态不属于钩子的职责范围。

Step 4: wire it in
settings.json

步骤4:在
settings.json
中配置

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": ".claude/hooks/{hook-name}.sh" }]
      }
    ]
  }
}
Without wiring, the hook file exists but never runs — the #1 cause of "my hook doesn't work."
json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": ".claude/hooks/{hook-name}.sh" }]
      }
    ]
  }
}
如果不配置,钩子文件虽然存在但永远不会运行 — 这是“我的钩子不工作”的头号原因。

Step 5: run the validator

步骤5:运行验证器

bash
bash skills/meta/create-or-audit-hook/lib/validate.sh .claude/hooks/{hook-name}.sh
bash
bash skills/meta/create-or-audit-hook/lib/validate.sh .claude/hooks/{hook-name}.sh

Step 6: dry-run

步骤6:试运行

bash
echo '{"tool_name":"Write","tool_input":{"file_path":"test.py","content":"x"}}' | \
  bash .claude/hooks/{hook-name}.sh
Confirm: relevant input acts; irrelevant input (wrong tool, wrong filetype) exits 0 silently.
bash
echo '{"tool_name":"Write","tool_input":{"file_path":"test.py","content":"x"}}' | \
  bash .claude/hooks/{hook-name}.sh
确认:相关输入会触发操作;无关输入(错误工具、错误文件类型)会静默退出并返回0。

Mode 2 — audit hooks

模式2 — 审核钩子

Step 1: structural on each

步骤1:逐个检查结构

bash
for f in .claude/hooks/*.sh; do
  echo "=== $f ==="
  bash skills/meta/create-or-audit-hook/lib/validate.sh "$f" | tail -5
done
bash
for f in .claude/hooks/*.sh; do
  echo "=== $f ==="
  bash skills/meta/create-or-audit-hook/lib/validate.sh "$f" | tail -5
done

Step 2: five gates

步骤2:五项检查

Gate 1 — wiring. Every hook file has a matching entry in
settings.json
. Orphans are dead code:
bash
jq -r '.hooks | to_entries[] | .value[] | .hooks[]?.command' .claude/settings.json \
  | grep -oE '[^/]+\.sh$' | sort -u > /tmp/wired.txt
ls .claude/hooks/*.sh | xargs -n1 basename | sort > /tmp/exists.txt
comm -23 /tmp/exists.txt /tmp/wired.txt    # hooks that exist but are not wired
comm -13 /tmp/exists.txt /tmp/wired.txt    # wirings that point to missing hooks
Gate 2 — tool presence checks.
PostToolUse
formatters must check for the tool (
command -v ruff >/dev/null 2>&1 || exit 0
) before running. Missing this makes the hook break silently on machines without the formatter.
Gate 3 — blocking discipline.
exit 2
without
>&2
output is broken. The user sees no message, only a refusal.
Gate 4 — no network.
curl
,
wget
,
fetch
in a hook means every tool call pays that latency. Move network-dependent work to a skill or a manual command.
Gate 5 — idempotency. Running the hook twice on the same input has the same effect. A
SessionStart
hook that appends to a log without a staleness check breaks idempotency.
检查1 — 配置情况。每个钩子文件在
settings.json
中都有对应的条目。孤立的钩子文件属于无效代码:
bash
jq -r '.hooks | to_entries[] | .value[] | .hooks[]?.command' .claude/settings.json \
  | grep -oE '[^/]+\.sh$' | sort -u > /tmp/wired.txt
ls .claude/hooks/*.sh | xargs -n1 basename | sort > /tmp/exists.txt
comm -23 /tmp/exists.txt /tmp/wired.txt    # 存在但未配置的钩子
comm -13 /tmp/exists.txt /tmp/wired.txt    # 配置指向不存在的钩子
检查2 — 工具存在性验证
PostToolUse
格式化钩子必须在运行前检查工具是否存在(
command -v ruff >/dev/null 2>&1 || exit 0
)。缺少此检查会导致在没有格式化工具的机器上钩子静默失效。
检查3 — 阻塞规范。仅
exit 2
但没有
>&2
输出的钩子是损坏的。用户只会看到操作被拒绝,却看不到任何提示信息。
检查4 — 无网络操作。钩子中出现
curl
wget
fetch
意味着每次工具调用都会产生网络延迟。将依赖网络的任务移至skill或手动命令中。
检查5 — 幂等性。对相同输入运行两次钩子应产生相同效果。例如,
SessionStart
钩子如果在没有过期检查的情况下追加日志,会破坏幂等性。

Step 3: report

步骤3:生成报告

markdown
undefined
markdown
undefined

Hooks Audit

钩子审核报告

Verdict per hook

各钩子判定结果

HookEventWired?ValidatorGates
protect-sensitive-files.shPreToolUseyesPASSall
...
钩子事件是否已配置验证器结果检查项
protect-sensitive-files.shPreToolUse通过全部通过
...

Orphan hooks (remove or wire)

孤立钩子(移除或配置)

...
...

Broken wirings (point to missing files)

无效配置(指向不存在的文件)

...
...

Proposed additions (hooks the codebase would benefit from)

建议新增钩子(代码库可从中受益的钩子)

...
undefined
...
undefined

Verify

验证

bash
bash skills/meta/create-or-audit-hook/lib/validate.sh .claude/hooks/{name}.sh
bash
bash skills/meta/create-or-audit-hook/lib/validate.sh .claude/hooks/{name}.sh

Expected: VERDICT: PASS

预期结果:VERDICT: PASS

Dry-run with a representative payload

使用代表性负载试运行

echo '{"tool_name":"Write","tool_input":{"file_path":"x.py"}}' | bash .claude/hooks/{name}.sh
undefined
echo '{"tool_name":"Write","tool_input":{"file_path":"x.py"}}' | bash .claude/hooks/{name}.sh
undefined

Common Mistakes

常见错误

MistakeCorrection
Hook file exists but isn't in
settings.json
Wire it. Claude Code only runs what's declared.
exit 1
when a formatter is missing
exit 0
instead. Missing local tooling should not block Claude's work.
exit 2
with no stderr message
Add
echo "BLOCKED: reason + use X instead" >&2
before the
exit 2
.
Reading
.tool_input.command
for a Write hook
Write's payload is
.tool_input.file_path
. Use
.command
only for
Bash
.
错误修正方案
钩子文件存在但未在
settings.json
中配置
进行配置。Claude Code仅运行已声明的钩子。
格式化工具缺失时
exit 1
改为
exit 0
。本地工具缺失不应阻止Claude的工作。
exit 2
但无标准错误输出
exit 2
之前添加
echo "BLOCKED: 原因 + 请使用X替代" >&2
针对Write钩子读取
.tool_input.command
Write的负载是
.tool_input.file_path
。仅对
Bash
钩子使用
.command