write-hook
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClaude Code hooks
Claude Code hooks
Behaviours observed in Claude Code web sessions, 2026-08-22 and 2026-08-23 - not inferred from docs.
2026-08-22 和 2026-08-23 在 Claude Code Web 会话中观察到的行为——并非根据文档推断。
Injecting context: the mechanism differs per event
注入上下文:不同事件采用的机制不同
| Event | How its output reaches the model |
|---|---|
| stdout on exit 0 is added to context as-is |
| stdout is NOT. Emit JSON with |
Plain text on stdout from a hook is the most common way to ship a hook that silently does
nothing: the script runs, the text goes to the debug log, the model never sees it.
Stopjson
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "..."
}
}| 事件 | 其输出如何到达模型 |
|---|---|
| 退出码为 0 时,stdout 会原样添加到上下文中 |
| stdout 不会到达模型。需要输出带有 |
Stopjson
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "..."
}
}A Stop
hook that changes what happens next MUST guard on stop_hook_active
Stopstop_hook_active会改变后续行为的 Stop
hook 必须以 stop_hook_active
作为防护依据
Stopstop_hook_activeInjecting context from does not just add text - it restarts the turn, which fires
again. Observed: four consecutive turns with no user input, burning tokens.
StopStopstop_hook_activebash
INPUT=$(cat)
if printf '%s' "$INPUT" | grep -Eq '"stop_hook_active"[[:space:]]*:[[:space:]]*true'; then
exit 0
fi从 注入上下文不只是添加文本——它还会重启当前轮次,从而再次触发 。曾观察到:在没有用户输入的情况下连续执行四轮,消耗 token。
StopStopstop_hook_activebash
INPUT=$(cat)
if printf '%s' "$INPUT" | grep -Eq '"stop_hook_active"[[:space:]]*:[[:space:]]*true'; then
exit 0
fiScope of the rule
规则的适用范围
- Applies to , because the event can re-fire itself. Whatever the hook emits to change what happens next, the guard applies - the trigger is the event, not the output channel.
Stop - Does NOT apply to or
SessionStart. Adding the check there is cargo cult.UserPromptSubmit - A hook that only reads state (logs, metrics) and emits nothing needs no guard.
Stop
- 适用于 ,因为该事件可能再次触发自身。无论 hook 输出什么来改变后续行为,防护规则都适用——触发因素是事件本身,而不是输出通道。
Stop - 不适用于 或
SessionStart。在这些事件中添加这项检查属于 cargo cult 式盲目照搬。UserPromptSubmit - hook 如果只读取状态(日志、指标)且不输出任何内容,则不需要防护。
Stop
Nothing substitutes for the guard
没有任何机制可以替代该防护
stop_hook_active| Substitute seen in review | Why it is not one |
|---|---|
| Marker file, once-per-session flag | Not turn-scoped. Silences the hook for the rest of the session, and a stale or wiped file changes behaviour |
Matching the reminder text in | Depends on the model repeating a string. A guard that the model can decline to honour is not a guard |
| "It exits 0 / never blocks / is idempotent" | Describes the exit path, not the re-fire. See the table below |
stop_hook_active| 审查中出现的替代方案 | 为何不算替代方案 |
|---|---|
| 标记文件、每会话仅执行一次的标志 | 并非按轮次限定作用域。它会让 hook 在会话余下时间中不再执行,而文件过期或被清除也会改变行为。 |
在 | 取决于模型是否重复某个字符串。一个可能被模型无视的防护并不是真正的防护。 |
| “它以 0 退出/从不阻塞/具备幂等性” | 描述的是退出路径,而不是重复触发问题。参见下表。 |
Rationalizations
常见理由
| Excuse | Reality |
|---|---|
" | This is the reasoning that produced the four-turn cascade. Injecting context restarts the turn on its own; blocking is a separate mechanism |
"My dedupe is strictly stronger than | Stronger at something else. It is session-scoped, not turn-scoped, and it lives outside the harness |
| "Add the guard after the demo / after the release" | Four lines. The cascade fires on the first live turn, in front of whoever is watching |
| "The reviewer said it is cargo cult" | Ask them for a |
| 借口 | 实际情况 |
|---|---|
“ | 正是这种思路引发了连续四轮的级联重复。注入上下文本身就会重启当前轮次;阻塞是另一套独立机制。 |
“我的去重逻辑严格强于 | 它只是在另一个方面更强。它按会话限定作用域,而不是按轮次,而且位于 harness 之外。 |
| “演示之后/发布之后再添加防护” | 只需要四行代码。级联重复会在第一次实际运行的轮次中当场触发,并出现在任何观察者眼前。 |
| “审查者说这是 cargo cult” | 要求他们提供一个会输出 |
Red flags - STOP
危险信号——STOP
- A hook that emits output but whose script never reads stdin.
Stop - The words "non-blocking", "idempotent" or "only fires once" offered as the reason no guard is needed.
- A guard that greps the model's own output instead of the hook's input.
- Shipping now, guarding later.
- hook 会输出内容,但其脚本从不读取 stdin。
Stop - 把“non-blocking”“idempotent”或“only fires once”等说法当作无需防护的理由。
- 防护逻辑对模型自己的输出进行 grep,而不是读取 hook 的输入。
- 现在先发布,之后再补防护。
Cloud sessions
云端会话
| Fact | Consequence |
|---|---|
Hooks in a repo's | Commit the hook, or it does not exist in a web session |
| Never make persistence depend on it. Commit and push as work is produced |
| The only dependable per-turn hook point |
| 事实 | 影响 |
|---|---|
仓库 | 请提交该 hook,否则它在 Web 会话中并不存在。 |
| 绝不要让持久化依赖它。每完成一项工作就提交并推送。 |
| 唯一可靠的按轮次 hook 触发点 |
Before shipping a hook
发布 hook 之前
- Feed it the JSON it will receive on stdin, for each branch - field absent, false, true.
- Check the exit code and the exact bytes on stdout.
- For a hook, confirm the guard path emits nothing.
Stop
- 针对每个分支,向它输入实际会从 stdin 收到的 JSON——分别覆盖字段缺失、false 和 true 的情况。
- 检查退出码和 stdout 上的确切字节。
- 对于 hook,确认防护路径不会输出任何内容。
Stop