<!-- title: 动态注入钩子跑通无头Agent -->
# 动态注入钩子跑通无头 Agent：用全生命周期钩子驱动 -p 自主流水线

## 概述

本教程聚焦 Claude Code 最容易被忽视的"上下文注入类"钩子——`UserPromptSubmit`、`SessionStart`、`Notification`、`SubagentStop`，把它们与 `-p` 无头模式、`--output-format stream-json` 机器可读输出、`TodoWrite` 自主规划组合成一条**零干预自主流水线**。学完你能让一个无头 Agent 在没有人在场时，自动加载环境、每轮注入新鲜上下文、改完代码自检、需要人时主动提醒、子代理产出先校验再放行。适合已写过 Skill/MCP、想从"交互式助手"迈向"无人值守 Agent"的开发者。

## 前置条件

- Claude Code v2.1.196+（钩子超时提示、`prompt_id`、`effort` 等字段需要较新版本；`terminalSequence` 需 v2.1.141+）
- 一个 Git 仓库（用来演示"分支体检"场景）
- `jq`、`bash`、`python3`（解析钩子 stdin 的 JSON 与处理流式输出）
- 已通读官方 hooks-guide，理解 `PreToolUse`/`PostToolUse` 基本用法（本篇不再重复安全钩子与格式化钩子的入门内容）

## 为什么换一个角度：四类"活水钩子"才是自主化的关键

近几轮教程已经把"安全钩子围栏""双钩格式化""Stop 钩子自愈跑测试""-p 流式批量引擎"讲透了。它们的共同点是：**钩子作为"闸门"或"批处理器"**——拦命令、跑格式、批量分发。

但真正决定一个无头 Agent 能不能"自己活下去"的，是另一组钩子：**把新鲜上下文像活水一样持续灌进模型上下文窗口的那一类**。官方文档把它们归为三种节奏：

- **每会话一次**：`SessionStart` / `SessionEnd`
- **每轮一次**：`UserPromptSubmit` / `Stop` / `StopFailure`
- **每个工具调用**：`PreToolUse` / `PostToolUse`

`UserPromptSubmit` 和 `SessionStart` 的关键能力是——**stdout 与 `additionalContext` 会被直接塞进模型上下文**（`PreToolUse` 的 stdout 只进调试日志，不给模型看）。这就是"注入"二字的来源。本篇的全部技巧都建立在这一点上。

> 与已覆盖主题的区别：本篇不写"批量分发"（那是并行/批量引擎）、不写"崩溃续传"（那是 --resume 断点）、不写"安全围栏"（那是 PreToolUse deny）。本篇写的是**单一深度任务的"上下文供给系统"**：让一个 -p 调用从启动到停止，全程被钩子动态喂上下文、被钩子校验、被钩子通知。

## 第一步：理解钩子配置的三层嵌套

所有钩子都写在 `settings.json`（用户级 `~/.claude/settings.json` 或项目级 `.claude/settings.json`）的 `hooks` 字段下，结构是三层：

```
hooks
 └── <事件名>            # 如 UserPromptSubmit
      └── [ matcher组 ]  # 过滤何时触发
           └── hooks: [  # 真正执行的 handler
                 { type, command/args/url/... }
               ]
```

`UserPromptSubmit`、`Stop`、`SubagentStop`、`Notification` 这几个事件**不支持 matcher**，写了也会被静默忽略——它们每次都触发。这点很重要，省得你困惑"为什么 matcher 没生效"。

每个 handler 有五种类型：`command`（跑 shell）、`http`（POST 到端点）、`mcp_tool`（调 MCP 工具）、`prompt`（让模型单轮判定 yes/no）、`agent`（起子代理带工具去校验，实验性）。本篇主要用 `command`，因为它最可控、最易调试。

## 第二步：SessionStart——会话启动即注入"初始态势"

`SessionStart` 在会话开始或 `--resume` 恢复时触发。它的输入里有 `source`（`startup`/`resume`/`clear`/`compact`）、可选的 `model`、`agent_type`、`session_title`。最关键的是两个输出能力：

1. **`additionalContext`**：在第一轮 prompt 之前就塞进上下文
2. **`initialUserMessage`**：在 `-p` 非交互模式下，直接成为"第一轮用户消息"——即使你没传 prompt

`initialUserMessage` 是自主化的利器：你可以让 SessionStart 钩子读一个工单文件，把它转成第一句指令，Agent 就"自己开口干活"了。

另外，SessionStart 钩子能拿到 `CLAUDE_ENV_FILE`——一个文件路径，你往里写 `export` 语句，这些变量会持久化到本次会话后续所有 Bash 命令里。

先写一个 SessionStart 钩子脚本 `.claude/hooks/on-start.sh`：

```bash
#!/bin/bash
# .claude/hooks/on-start.sh —— 注入初始态势 + 持久化环境变量
input=$(cat)
source=$(jq -r '.source' <<< "$input")

# 1) 持久化环境变量到后续 Bash 命令
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

# 2) 采集初始态势（活水第一瓢）
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "no-git")
dirty=$(git status --porcelain 2>/dev/null | head -20)
last_ci=$(grep -rhoE "FAIL|ERROR" .github/workflows/*.yml 2>/dev/null | head -5)

# 3) 用 additionalContext 注入，source=resume 时刷新过期信息
jq -nc --arg ctx "环境态势 [source=$source]:
- 分支: $branch
- 未提交变更:
$dirty
- 近期 CI 关键词: ${last_ci:-无}
注意: 时间戳/SHA 在 resume 时会过期，本钩子已重新采集。" \
  '{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:$ctx}}'
```

预期结果：会话一启动，模型上下文里就有了当前分支、脏文件清单、CI 关键词，**不需要你再说一遍"先看看现在在哪个分支"**。

常见问题：

- **resume 后信息过期**：文档明确——`PostToolUse`/`UserPromptSubmit` 注入的文本在 resume 时是"回放"而非重跑；只有 `SessionStart` 在 `source=resume` 时会**重新执行**。所以把"需要保鲜"的态势采集放在 SessionStart 里。
- **stdout vs JSON 二选一**：SessionStart 的 plain stdout 也会进上下文，但你要同时设 `sessionTitle` 就必须用 JSON 形式。两者不能混用输出语义。

## 第三步：UserPromptSubmit——每轮注入"活水"

这是本篇的主角。它在**每次提交 prompt 后、模型处理前**触发，输入里有 `prompt` 字段（用户原话）。默认超时 30 秒（比其它事件的 600 秒短），因为它阻塞模型处理——卡住就整个会话卡住。

它有两种"加上下文"的方式：

- **plain stdout**：非 JSON 文本直接进上下文，且**会在 transcript 里显示**
- **JSON `additionalContext`**：包在 `hookSpecificOutput` 里，作为**系统提醒**注入，模型读得到但 transcript 里不显示为消息

自主场景推荐用 `additionalContext`：保持 transcript 干净，又能每轮把"此刻的真实状态"喂给模型。

写一个 `.claude/hooks/inject-context.sh`，每轮注入 git diff 统计 + 项目规约摘要：

```bash
#!/bin/bash
# .claude/hooks/inject-context.sh —— 每轮注入活水
# 超时 30s，只做轻量采集，重活留给 SessionStart 或子代理
diffstat=$(git diff --stat 2>/dev/null | tail -15)
todo_count=$(grep -rEc "TODO|FIXME" src/ 2>/dev/null | paste -sd+ | bc 2>/dev/null || echo 0)
convention=$(head -40 .claude/conventions.md 2>/dev/null || echo "无显式规约文件")

jq -nc --arg ctx "[每轮活水注入]
- 当前 diff 统计:
$diffstat
- 仓库内 TODO/FIXME 计数: $todo_count
- 必须遵守的规约摘要:
$convention
约束: 改动前先确认对应测试存在；禁止提交 .env" \
  '{hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:$ctx}}'
```

把两个钩子注册进 `.claude/settings.json`：

```json
{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/on-start.sh" } ] }
    ],
    "UserPromptSubmit": [
      { "hooks": [ { "type": "command", "timeout": 20,
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/inject-context.sh" } ] }
    ]
  }
}
```

预期结果：无论你（或 -p 脚本）发什么 prompt，模型每轮都能看到"此刻"的 diff、TODO 计数、规约。**信息不再过期**——这是"活水"相比写死在 CLAUDE.md 里"死水"的根本优势。

常见问题：

- **输出超 10000 字符**：`additionalContext` 上限 10000 字符，超出会被存进文件、只传"预览+文件路径"给模型。所以 `head`/`tail` 限制行数是必须的。
- **超时被丢弃**：v2.1.196 起，超时会丢弃输出并在 transcript 提示钩子名与超时值；旧版本静默丢弃。重采集要在 `timeout` 内完成，采集慢的数据放 SessionStart。
- **想拒绝某个 prompt**：返回 `{"decision":"block","reason":"..."}` 会**擦除**该 prompt 不让模型处理——可用于"非工作时间禁止改生产分支"之类的策略。

## 第四步：Stop——自主校验与"不许停"

`Stop` 在模型每轮回答结束时触发。输入里有三个关键字段：

- `stop_hook_active`：为 `true` 表示"本次继续就是因为上一个 Stop 钩子让继续的"——**必须检查它**，否则会卡在永不满足的条件上
- `last_assistant_message`：本轮最终回答的文本，**别去读 transcript**（transcript 异步写入，Stop 时不一定含最终消息）
- `background_tasks` / `session_crons`：区分"真结束"还是"在等后台任务唤醒"

返回 `decision:"block"` 或 `additionalContext` 都能让会话**继续**（区别在于 transcript 标注与是否显示 hook 错误通知）。官方有**连续 8 次阻断上限**：超过 8 次强制结束，防死循环。

写一个自检 Stop 钩子 `.claude/hooks/verify-and-continue.sh`：

```bash
#!/bin/bash
# .claude/hooks/verify-and-continue.sh —— 改完跑校验，失败就让它继续修
input=$(cat)
active=$(jq -r '.stop_hook_active' <<< "$input")

# 已经是"被续命"状态，别再无脑 block
if [ "$active" = "true" ]; then
  exit 0
fi

# 跑轻量校验（测试留给专门的 verify 步骤，这里只 lint）
lint_out=$(npx --no-install eslint src/ --format compact 2>&1 | tail -30)
if echo "$lint_out" | grep -qE "error|Error"; then
  jq -nc --arg fb "[Stop 自检] lint 未通过，请修复后再结束:\n$lint_out" \
    '{hookSpecificOutput:{hookEventName:"Stop",additionalContext:$fb}}'
else
  exit 0
fi
```

注意：`additionalContext` 走的是和 `block` 相同的续命机制（受 `stop_hook_active` 与 8 次上限保护），但 transcript 标注为"Stop hook feedback"且**不显示 hook 错误通知**——所以"正常给指导"用 `additionalContext`，"硬拦"才用 `decision:block`。

## 第五步：Notification 与 terminalSequence——无人值守时主动叫人

`Notification` 在 Claude Code 发通知时触发（权限提示、空闲、鉴权成功、子代理需要输入等）。它**不能 block**，但能做两件事：把消息发外部（webhook/桌面通知），或用 `terminalSequence` 让终端响铃/弹通知。

钩子运行环境**没有控制终端**（v2.1.139 起 Linux/macOS 钩子在自己的会话里，打不开 `/dev/tty`），所以直接写转义序列会失败。正确做法是返回 `terminalSequence`，由 Claude Code 替你通过它的终端写入路径发出——这是 race-free 的，且在 tmux/screen/Windows 都能用。允许列表限 OSC 0/1/2/9/99/777 与 BEL。

`.claude/hooks/notify.sh`：

```bash
#!/bin/bash
# .claude/hooks/notify.sh —— 桌面通知 + 可选 webhook
input=$(cat)
body=$(jq -r '.message // "需要你注意" ' <<< "$input")
title=$(jq -r '.title // "Claude Code" ' <<< "$input")

# OSC 777: urxvt/Ghostty/Warp 通用通知
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")

# 可选：同时打 webhook（无人值守机房的转发渠道）
curl -s -m 3 -X POST "http://localhost:8080/notify" \
  -H 'Content-Type: application/json' \
  -d "{\"title\":\"$title\",\"body\":\"$body\"}" >/dev/null 2>&1 &

jq -nc --arg seq "$seq" '{terminalSequence:$seq}'
```

注册时记得加 `notification_type` 不需要 matcher（Notification 支持 matcher，值为 `permission_prompt`/`idle_prompt`/`agent_completed` 等）：

```json
"Notification": [
  { "matcher": "idle_prompt|agent_completed",
    "hooks": [ { "type": "command",
      "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/notify.sh" } ] }
]
```

## 第六步：SubagentStop——子代理产出先校验再放行

当你用 Agent 工具派子代理干活时，`SubagentStop` 在子代理结束时触发。输入里有 `agent_id`、`agent_type`、`agent_transcript_path`、`last_assistant_message`。它的决策格式与 `Stop` 一致：`decision:"block"` + `reason` 会让子代理**继续运行**并把 reason 作为下一条指令喂回去——等于"你这个产出不合格，重做"。

这是"自主但受控"的关键一环：让主 Agent 派子代理做调研，子代理回来前先用钩子检查"是否真带了证据"。

```bash
#!/bin/bash
# .claude/hooks/check-subagent.sh —— 子代理必须带回文件证据
input=$(cat)
msg=$(jq -r '.last_assistant_message' <<< "$input")
# 简单规则：回答里若没有任何 src/ 路径引用，视为没落地
if ! echo "$msg" | grep -qE "src/|test/"; then
  jq -nc '{decision:"block", reason:"子代理产出未引用任何源码路径，请实际打开文件确认后再总结。"}'
else
  exit 0
fi
```

> 文档提醒：若想把子代理结果注入**父会话**上下文，别用 SubagentStop（它只对子代理生效），改用对 `Agent` 工具的 `PostToolUse` 钩子。

## 第七步：-p 无头驱动 + stream-json 机器可读输出

钩子搭好"供给系统"后，用一个 bash 脚本以无头方式驱动整条流水线。核心是 `-p` 与一组关键 flag：

```bash
#!/bin/bash
# run-agent.sh —— 无头驱动自主分支体检
set -euo pipefail

SESSION="polish-$(date +%s)"
PROMPT_FILE="${1:-.claude/task.md}"

claude -p \
  --output-format stream-json \
  --include-partial-messages \
  --verbose \
  --session-id "$SESSION" \
  --permission-mode acceptEdits \
  --max-turns 30 \
  --allowedTools "Read,Write,Edit,Bash,Grep,Glob,TodoWrite" \
  --disallowedTools "WebFetch" \
  --add-dir "$(pwd)" \
  --append-system-prompt "你是无人值守的分支体检 Agent。每轮会收到钩子注入的活水上下文。先用 TodoWrite 拆解任务，逐步执行，改完确保 lint 通过。" \
  "$(cat "$PROMPT_FILE")" \
  2>logs/agent-$SESSION.err \
  | tee logs/agent-$SESSION.stream.jsonl
```

逐个 flag 解读：

| flag | 作用 | 自主化意义 |
|---|---|---|
| `-p` / `--print` | 非交互，跑完即退 | 无人值守的前提 |
| `--output-format stream-json` | 每行一个 JSON 事件 | 机器可解析，能编程判定成败 |
| `--include-partial-messages` | 流式含增量文本 | 实时观察长任务进度 |
| `--session-id` | 指定会话 ID | 配合 `--resume` 断点续做 |
| `--permission-mode acceptEdits` | 自动接受文件编辑 | 避免 -p 卡在权限弹窗 |
| `--max-turns 30` | 上限轮数 | 防失控，与 Stop 8 次上限双保险 |
| `--allowedTools` / `--disallowedTools` | 工具白/黑名单 | 收紧自主权限面 |
| `--append-system-prompt` | 追加系统指令 | 注入角色与流程约束 |
| `--add-dir` | 增加可访问目录 | 跨目录只读访问 |

`stream-json` 输出里，最末一条 `type:"result"` 事件含 `subtype:"result"`、`result`（最终文本）、`is_error`、`duration_ms`、`total_cost_usd`、`num_turns`。解析它就能判定这次自主任务到底成没成：

```bash
# 取最终结果
tail -1 logs/agent-$SESSION.stream.jsonl \
  | jq '{ok: (.is_error|not), turns: .num_turns, cost: .total_cost_usd, text: .result}'
```

## 实战案例：无人值守"分支体检"流水线

把上面七步拼成一个完整可运行项目。目录结构：

```
myrepo/
├── .claude/
│   ├── settings.json
│   ├── task.md                  # 给无头 Agent 的任务说明
│   ├── conventions.md           # 项目规约（活水来源之一）
│   └── hooks/
│       ├── on-start.sh
│       ├── inject-context.sh
│       ├── verify-and-continue.sh
│       ├── notify.sh
│       └── check-subagent.sh
├── scripts/run-agent.sh
└── src/  (你的代码)
```

`task.md`（给 Agent 的任务）：

```markdown
目标: 对当前分支做"体检"并修复明显问题。
1. 用 TodoWrite 列出: lint、类型检查、TODO 清理、测试存在性核查。
2. 逐项执行；能安全修的（如未使用 import、格式）直接修。
3. 涉及逻辑的改动只记录到报告，不改。
4. 结束前确保 lint 通过（Stop 钩子会校验）。
```

完整 `settings.json`（合并五类钩子）：

```json
{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/on-start.sh" } ] }
    ],
    "UserPromptSubmit": [
      { "hooks": [ { "type": "command", "timeout": 20,
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/inject-context.sh" } ] }
    ],
    "PreToolUse": [
      { "matcher": "Bash",
        "hooks": [ { "type": "command", "if": "Bash(rm -rf *)|Bash(git push --force*)",
          "command": "jq -nc '{hookSpecificOutput:{hookEventName:\"PreToolUse\",permissionDecision:\"deny\",permissionDecisionReason:\"危险命令被自主围栏拦截\"}}'" } ] }
    ],
    "PostToolUse": [
      { "matcher": "Edit|Write",
        "hooks": [ { "type": "command",
          "command": "npx --no-install prettier --write \"${tool_input.file_path}\" 2>/dev/null; exit 0" } ] }
    ],
    "Stop": [
      { "hooks": [ { "type": "command",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/verify-and-continue.sh" } ] }
    ],
    "SubagentStop": [
      { "hooks": [ { "type": "command",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-subagent.sh" } ] }
    ],
    "Notification": [
      { "matcher": "idle_prompt|agent_completed",
        "hooks": [ { "type": "command",
          "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/notify.sh" } ] }
    ]
  }
}
```

注意 `PostToolUse` 里 `${tool_input.file_path}` 的用法——`tool_input` 是工具事件 stdin JSON 里的字段。但上面写法把变量直接拼进 `command` 字符串会拿不到值（钩子 command 里的 `${tool_input.file_path}` 不是 shell 变量替换，而是需要脚本从 stdin 读）。正确做法是脚本里 `jq -r '.tool_input.file_path'`。修正后的格式化钩子应改为：

```json
{ "matcher": "Edit|Write",
  "hooks": [ { "type": "command",
    "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh" } ] }
```

`format.sh`：

```bash
#!/bin/bash
f=$(jq -r '.tool_input.file_path' )
[ -f "$f" ] && npx --no-install prettier --write "$f" 2>/dev/null
exit 0
```

运行：`chmod +x .claude/hooks/*.sh && bash scripts/run-agent.sh .claude/task.md`

预期行为链：SessionStart 注入分支态势 → UserPromptSubmit 每轮注入 diff/规约 → Agent 用 TodoWrite 拆活 → 改文件触发 PostToolUse 自动 prettier → 危险命令被 PreToolUse 拦 → 每轮结束 Stop 跑 lint，不过就 additionalContext 续命让它修 → 子代理产出无证据被 SubagentStop 打回 → 完成或需要人时 Notification 响铃+webhook。全程无需你在场。

## Mermaid 流程图：全生命周期自主流水线

```mermaid
flowchart TD
    A[run-agent.sh -p 无头启动] --> B[SessionStart 注入初始态势+环境变量]
    B --> C[UserPromptSubmit 每轮注入活水 diff/规约/TODO]
    C --> D[Agent 用 TodoWrite 拆解并执行]
    D --> E{调用工具?}
    E -->|是| F[PreToolUse 拦危险命令]
    F --> G[工具执行]
    G --> H[PostToolUse 自动格式化]
    H --> D
    E -->|派子代理| I[SubagentStart]
    I --> J[SubagentStop 校验产出证据]
    J -->|不合格| I
    J -->|合格| D
    E -->|否/本轮结束| K[Stop 自检 lint]
    K -->|失败| L[additionalContext 续命]
    L --> D
    K -->|通过| M{需要人?}
    M -->|是| N[Notification 响铃+webhook]
    M -->|否| O[输出 stream-json result]
    N --> O
    O --> P[tail 解析 is_error/num_turns/cost]
```

## 调试与常见坑

- **钩子没触发**：先用 `/hooks` 菜单确认已加载；`UserPromptSubmit`/`Stop` 等不支持 matcher，别在它们上面写 `matcher`。
- **JSON 解析失败**：shell profile 若在启动时打印文本会污染 stdout。文档明确"stdout 必须只含 JSON 对象"。
- **exit 1 不拦**：除 `WorktreeCreate` 外，**只有 exit 2 才是阻断**；exit 1 是非阻断错误。要 enforce 策略就用 exit 2 或 JSON `decision:"block"`。
- **resume 后旧上下文**：`UserPromptSubmit`/`PostToolUse` 注入的内容在 resume 时是回放（含过期时间戳/SHA）；把"必须保鲜"的采集放进 SessionStart（resume 会重跑）。
- **Stop 死循环**：务必检查 `stop_hook_active`，并知道 8 次连续阻断会被强制结束。
- **`-p` 卡住不退**：多半是权限弹窗——`--permission-mode acceptEdits` 或更严格的 `--allowedTools` 白名单能解决；不要为了省事直接 `bypassPermissions`。

## 延伸阅读

- 官方 hooks-guide：从零配第一个钩子，配合本篇食用
- 官方 hooks reference：所有事件 schema 与 `hookSpecificOutput` 字段全表
- `async`/`asyncRewake` 后台钩子：让长任务（跑测试）不阻塞主循环，exit 2 时唤醒 Claude
- `prompt`/`agent` 类型钩子：用模型本身做 yes/no 判定或带工具校验，适合"语义级围栏"
- `--input-format stream-json`：把 Claude Code 当作可编程对话引擎，往里灌结构化消息（含工具结果），构建更复杂的多阶段编排

## 训练营适用性评估

- **适合类型**：进阶 DevOps/平台工程训练营、AI 工程化实战营、内部研发效能提升营。不适合纯入门营——需要对 Git、shell、JSON、Agent 概念有基础。
- **教学时长**：约 4–5 小时（讲解 1.5h + 动手搭五类钩子 2h + 调试与流水线联调 1.5h）。
- **学员产出**：一套可提交进仓库的 `.claude/settings.json` + hooks 脚本 + 一个 `run-agent.sh` 无头驱动器。
- **延伸练习**：把 `Notification` 钩子的 webhook 接到飞书/钉钉；把 `SubagentStop` 的校验规则升级为 `agent` 类型钩子做语义判定；用 `--resume` 把失败任务接续重跑。

## 参考来源

1. [Hooks reference — Claude Code Docs](https://code.claude.com/docs/en/hooks) — 官方钩子事件全表、JSON 输入输出 schema、`additionalContext`/`terminalSequence`/`hookSpecificOutput` 字段与 exit code 行为的权威定义
2. [Automate actions with hooks — Claude Code Docs](https://code.claude.com/docs/en/hooks-guide) — 官方快速上手指南，讲解三层配置与 matcher/if 过滤
3. [Claude Code Hooks: Complete Guide with 20+ Ready-to-Use Examples (2026) — aiorg.dev](https://aiorg.dev/blog/claude-code-hooks) — 覆盖 UserPromptSubmit 上下文注入、Stop 续命、Notification 通知等实战配置范例
4. [Session and Context Hooks — DeepWiki (disler/claude-code-hooks-mastery)](https://deepwiki.com/disler/claude-code-hooks-mastery/3.2-session-and-context-hooks) — 社区对 SessionStart/UserPromptSubmit 等"会话与上下文钩子"生命周期与 `CLAUDE_ENV_FILE` 的深入解读
5. [Claude Code: Hooks, Subagents, and Skills — Complete Guide (dev.to)](https://dev.to/owen_fox/claude-code-hooks-subagents-and-skills-complete-guide-hjm) — 把钩子、子代理、Skill 串成"可编程 AI"视角的社区教程，SubagentStop 校验思路参考自此

> 本文由 Claude Code 实战教程系列整理，聚焦"上下文注入类钩子 + -p 无头模式"的自主化路径。文中命令与 schema 基于 Claude Code v2.1.196+ 官方文档核对，实际使用请以本地版本为准。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
