<!-- title: 造上下文管家钩子：注入与抢救 -->

# 造上下文管家钩子：自动注入与抢救记忆

## 概述

本教程聚焦 Claude Code 三类常被忽视的「上下文生命周期钩子」——`SessionStart`、`UserPromptSubmit`、`PreCompact`，带你从零搭建一个**项目记忆管家**：会话启动即注入状态、每条提问按关键词补充相关文档、压缩前把目标抢救到磁盘。读完即可让 Claude 在长会话和 `-p` 批处理中始终保持「记得自己在做什么」。适合已跑通首个 Claude Code 项目、想进阶自动化的开发者。

## 前置条件

- 已安装 Claude Code CLI（`claude --version` 可用），跑通过至少一个交互式或 `-p` 任务
- 本机有 `bash`、`jq`、`git`（`jq` 是解析钩子 stdin 的核心，没有就 `brew install jq` / `apt install jq`）
- 一个用于练习的 git 项目目录（含若干源码文件即可，无仓库也能跑，但 git 摘要功能会跳过）
- 了解 `settings.json` 基本结构（项目级放在 `.claude/settings.json`，用户级放在 `~/.claude/settings.json`）

## 一、为什么是这三类钩子

Claude Code 的 8 类钩子事件，社区教程九成都在讲 `PreToolUse` / `PostToolUse`（拦危险命令、自动格式化）。但真正决定 Claude「记不记得住事」的，是另外三类的生命周期钩子：

| 钩子事件 | 触发时机 | 最擅长的事 |
|---|---|---|
| `SessionStart` | 新会话、`--resume` 恢复、`/clear` 清屏、`/compact` 压缩后 | 启动即注入项目状态、上次笔记、git 摘要 |
| `UserPromptSubmit` | 用户按下回车、提问进入 Claude **之前** | 按关键词路由，给这条提问补充相关上下文 |
| `PreCompact` | `/compact` 手动或上下文将满自动压缩**之前** | 把目标、待办、关键决策抢救到磁盘，防止压缩丢失 |

这三者的共同点是：**它们的 stdout（exit 0）能被注入进 Claude 的上下文**（`PreCompact` 例外，它的 stderr 只给用户看，本身不注入——它的活儿是写盘抢救）。这恰好补上了 `CLAUDE.md` 静态记忆做不到的两件事：**动态、按需、跨压缩持续**。

`CLAUDE.md` 是「永远在场」的全局记忆，但它是静态的、全文常驻、不分场景。而上下文管家钩子是「按需召唤」的：启动时给一份当前状态，提问时按内容补一两段相关文档，压缩前把要紧的东西先存盘。两者互补，不是替代。

> ⚠️ 重要区别：这三类钩子和 `PreToolUse`/`PostToolUse` 的 exit code 2 行为不同。对 `SessionStart`/`UserPromptSubmit`/`PreCompact`，exit code 2 的 stderr **只给用户看，不进上下文，也不阻断流程**（`UserPromptSubmit` 的 exit 2 会阻断该提问并擦除）。这是后面调试时最常踩的坑。

## 二、钩子脚手架与三种验证方法

先把目录和测试方法备好，避免后面改一次就要重启会话。

**目录结构：**

```
your-project/
├── .claude/
│   ├── settings.json          # 注册三类钩子
│   ├── hooks/
│   │   ├── on-session-start.sh
│   │   ├── on-prompt-submit.sh
│   │   └── on-precompact.sh
│   ├── context/               # UserPromptSubmit 路由用的知识片段
│   │   ├── testing.md
│   │   ├── deploy.md
│   │   └── database.md
│   └── notes/                 # PreCompact 抢救出来的笔记（自动生成）
└── STATUS.md                  # 人机共维护的项目状态
```

`STATUS.md` 是个简单约定，内容示例：

```markdown
# 当前目标
- 把 src/ 里的错误处理统一改成返回 Result 类型
- 修复 #142：登录偶发 500

# 已知约束
- 不得引入新依赖，预算已锁
```

**三种验证方法（按从快到慢排列）：**

1. **管道喂数据**（最快，不依赖 Claude）：直接构造一份 stdin JSON 喂给脚本，看 stdout。
   ```bash
   echo '{"hook_event_name":"SessionStart","source":"startup","cwd":"'"$PWD"'","session_id":"t1","transcript_path":""}' \
     | bash .claude/hooks/on-session-start.sh
   ```
   能看到拼接好的上下文片段，就说明逻辑对了。

2. **`/hooks` 命令**：在交互式会话里输入 `/hooks`，会列出当前生效的所有钩子及其事件，能确认配置有没有被正确加载。

3. **transcript 回看（Ctrl+R）**：钩子注入的上下文会出现在 transcript 里。按 `Ctrl+R` 进入 transcript 模式，能看到「这条提问注入了什么」「会话启动注入了什么」，是排查「为什么没生效」的终极手段。

> 关键心法：对 `SessionStart` 和 `UserPromptSubmit`，**脚本的 stdout 会被原样注入 Claude 上下文**。这意味着脚本里任何一句调试用的 `echo "debug..."` 都会污染上下文。调试一律走 stderr（`echo ... >&2`），它只给用户看，不进上下文。

## 三、SessionStart：启动即注入当前状态

会话一开（或 `/clear`、`--resume`、`/compact` 之后），Claude 的上下文是「干净」的。这正是注入项目状态的最佳时机。

**`.claude/hooks/on-session-start.sh`：**

```bash
#!/usr/bin/env bash
# SessionStart 钩子：会话启动/恢复/清屏/压缩后注入项目上下文
set -euo pipefail

input=$(cat)
source=$(printf '%s' "$input" | jq -r '.source // "startup"')
cwd=$(printf '%s' "$input" | jq -r '.cwd // "."')
cd "$cwd" 2>/dev/null || exit 0   # 进不去项目目录就静默退出

ctx=""
ctx+="## 🧠 项目记忆管家 · 会话上下文（来源: $source）\n\n"

# 1) 上次 PreCompact 抢救的笔记：取最近一份
latest=$(ls -t .claude/notes/*.md 2>/dev/null | head -1)
if [ -n "$latest" ]; then
  ctx+="### 🧷 上次压缩前抢救的笔记（$latest）\n$(sed -n '1,30p' "$latest")\n\n"
fi

# 2) STATUS.md：人机共维护的当前目标
if [ -f STATUS.md ]; then
  ctx+="### 📋 STATUS.md\n$(sed -n '1,40p' STATUS.md)\n\n"
fi

# 3) Git 摘要：当前分支 + 未提交改动数
if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
  branch=$(git branch --show-current 2>/dev/null || echo "?")
  dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
  ahead=$(git rev-list --count '@{u}..HEAD' 2>/dev/null || echo "0")
  ctx+="### 🌿 Git：分支=$branch  未提交=$dirty  领先远端=$ahead\n\n"
fi

# stdout + exit 0 → 这段文本被注入上下文
printf '%b' "$ctx"
exit 0
```

**逐行说明：**

- `set -euo pipefail`：任意一步失败立即退出，避免半截错误输出污染上下文。
- `cd "$cwd"`：钩子默认在项目目录运行，但显式 `cd` 更稳妥；进不去就 `exit 0` 静默放行（钩子不应阻断会话启动）。
- `printf '%b'`：让 `\n` 真正换行；用 `echo -e` 在不同 shell 行为不一致。
- 最后 `exit 0` 是关键：exit 0 + 非空 stdout = 注入上下文。

**预期结果**：会话一启动，Claude 就「知道」当前在哪个分支、有几个未提交改动、上次压缩前在忙什么、当前目标是什么。你不用每次开场都重复「我们在做一个登录修复」。

**常见问题：**

- **没注入**：八成是脚本报错走了非零 exit。先用方法 1 的管道单独跑脚本，看 stderr。
- **注入了一堆报错**：`set -e` 让某条命令失败导致脚本中途退出，但前半段 `ctx+=` 已经拼了——把不可靠的命令（如 `git rev-list` 依赖远端）用 `|| true` / `2>/dev/null` 兜底。
- **source 字段**：官方枚举为 `startup`/`resume`/`clear`/`compact`。可以据此区分行为——比如 `resume` 时少注入点（用户大概率记得上下文），`clear`/`compact` 后多注入点（上下文被清空了）。

## 四、UserPromptSubmit：按关键词路由补充上下文

`SessionStart` 是「开场白」，`UserPromptSubmit` 才是「随叫随到」。它在提问进入 Claude **之前**触发，收到 `prompt` 字段，可以分析提问内容并补充相关文档。

这解决一个真实痛点：项目文档全堆进 `CLAUDE.md` 会撑爆上下文；不堆又导致 Claude「不知道有这套规范」。`UserPromptSubmit` 让上下文**按需进场**。

**`.claude/hooks/on-prompt-submit.sh`：**

```bash
#!/usr/bin/env bash
# UserPromptSubmit 钩子：按提问关键词路由补充相关上下文片段
set -euo pipefail

input=$(cat)
prompt=$(printf '%s' "$input" | jq -r '.prompt // ""')
cwd=$(printf '%s' "$input" | jq -r '.cwd // "."')
cd "$cwd" 2>/dev/null || exit 0

[ -d .claude/context ] || exit 0   # 没有知识目录就不补

add=""
# 关键词 → 上下文片段的路由表
if printf '%s' "$prompt" | grep -qiE '测试|test|单测|tdd|覆盖率'; then
  add+="$(cat .claude/context/testing.md 2>/dev/null)\n\n"
fi
if printf '%s' "$prompt" | grep -qiE '部署|deploy|发布|上线|prod|回滚'; then
  add+="$(cat .claude/context/deploy.md 2>/dev/null)\n\n"
fi
if printf '%s' "$prompt" | grep -qiE '数据库|db|sql|表结构|schema|migration|索引'; then
  add+="$(cat .claude/context/database.md 2>/dev/null)\n\n"
fi

# 没命中任何关键词就静默退出，不污染上下文
[ -z "$(printf '%b' "$add" | tr -d '[:space:]')" ] && exit 0

# 结构化输出：additionalContext 会被附加到这条提问的上下文里
jq -nc --arg ctx "## 📎 相关上下文（按提问关键词自动补充）\n\n${add}" \
  '{hookSpecificOutput:{hookEventName:"UserPromptSubmit", additionalContext:$ctx}}'
```

**知识片段示例** `.claude/context/testing.md`：

```markdown
# 测试规范
- 单测用 Vitest，文件放 src/__tests__/，命名 *.test.ts
- 禁止 mock 数据库，用 testcontainers 起真实 PG
- 覆盖率门槛：核心模块 ≥ 90%，工具函数 ≥ 80%
- 提交前必须跑 `npm run test:cov`
```

**两种输出方式的选择：**

- **stdout 纯文本（exit 0）**：简单粗暴，整段进上下文。适合临时、快速。
- **`hookSpecificOutput.additionalContext`（JSON）**：推荐，语义明确，且和「对用户的回复」区分清晰。

**预期结果**：你输入「给登录函数补单测」，钩子命中 `测试|单测`，自动把 `testing.md` 的规范塞进上下文。Claude 生成的测试会直接遵守「用 Vitest、不 mock 数据库、放 `__tests__/`」，而不是自作主张用 Jest。

**进阶：语义路由而非关键词**

关键词 grep 简单可靠，但「帮我把用户表加个索引」里没有「数据库」三个字却命中了 `索引`。要更准，可以调用一个本地嵌入模型或直接在脚本里 `curl` 一个轻量分类接口。但**先用关键词跑通**，80% 场景够用，别过早优化。

**常见问题：**

- **每次提问都注入同样大段**：检查是否漏了「没命中就静默退出」的判断，否则任何提问都拖一坨无关上下文。
- **阻塞了提问**：`UserPromptSubmit` 的 exit code 2 会**擦除该提问并阻断**。脚本里别手滑返回非零。想阻断（如检测到提问含密钥），明确用 `exit 2` 并把提示写到 stderr。
- **`grep -q` 退出码 1 被 `set -e` 杀掉**：`grep` 没匹配返回 1，`set -e` 会误判为失败。用 `if grep -q ...; then` 包裹（`if` 里的命令不受 `set -e` 影响），或加 `|| true`。

## 五、PreCompact：压缩前抢救关键信息

长会话里上下文迟早会满，Claude Code 自动 `/compact`，或你手动压缩。压缩会丢弃大量中间细节，**目标和待办尤其容易丢**。`PreCompact` 在压缩**执行之前**触发，是「抢救黄金窗口」。

**`.claude/hooks/on-precompact.sh`：**

```bash
#!/usr/bin/env bash
# PreCompact 钩子：压缩前把最近目标/待办抢救到 .claude/notes
set -euo pipefail

input=$(cat)
trigger=$(printf '%s' "$input" | jq -r '.trigger // "manual"')   # manual | auto
tpath=$(printf '%s' "$input" | jq -r '.transcript_path // ""')
sid=$(printf '%s' "$input" | jq -r '.session_id // "unknown"')

mkdir -p .claude/notes 2>/dev/null || true

# 从 transcript 抽取最近的若干条 user 消息作为「目标」抢救
rescued=""
if [ -n "$tpath" ] && [ -f "$tpath" ]; then
  rescued=$(jq -r 'select(.type=="user") | .message.content
                   | if type=="array" then (map(select(.type=="text").text)|join(" "))
                     else . end' "$tpath" 2>/dev/null \
           | grep -vE '^\s*$' | tail -n 25 || true)
fi

out=".claude/notes/session-${sid}.md"
{
  echo "# 压缩前抢救 · trigger=$trigger · $(date -u +%FT%TZ 2>/dev/null)"
  echo
  echo "## 最近用户目标（最近 25 条）"
  echo "${rescued:-（未抽取到用户消息）}"
} > "$out"

# PreCompact 的 stderr 只给用户看，不进上下文——适合做日志
echo "[记忆管家] trigger=$trigger 已抢救最近目标 → $out" >&2
exit 0
```

**注意 `PreCompact` 的特殊性**：

- 它**没有** `additionalContext` 输出，stdout/stderr 都**不注入** Claude 上下文。它的使命是**写盘**，把信息存到 `.claude/notes/`，等**下一次** `SessionStart` 把它读回来。
- 这就形成了一个闭环：`PreCompact`（压缩前抢救）→ 磁盘 → `SessionStart`（下次启动读回）。压缩丢的信息，靠磁盘中转回来了。
- `.message.content` 在 transcript 里可能是字符串也可能是数组（含图片/工具结果），上面的 jq 用 `if type=="array"` 兼容两种形态——这是真实 transcript 解析最常踩的坑。

**预期结果**：每次压缩前，最近的目标和指令被存盘。即便会话被压缩多次、甚至重启，`SessionStart` 都能把上次的「我们在修 #142」捞回来。

**常见问题：**

- **transcript 路径不对**：`transcript_path` 是 `.jsonl` 文件，钩子里直接 `jq` 逐行解析即可。若文件很大，`tail` 限制行数，别一次性读全。
- **抢救了太多噪声**：用户消息里混了工具调用结果。用 `select(.type=="user")` 过滤，并进一步排除 `tool_result`。

## 六、注册钩子：settings.json

三类脚本就绪，统一注册到项目级 `.claude/settings.json`：

```json
{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/on-session-start.sh" } ] }
    ],
    "UserPromptSubmit": [
      { "hooks": [ { "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/on-prompt-submit.sh" } ] }
    ],
    "PreCompact": [
      { "hooks": [ { "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/on-precompact.sh" } ] }
    ]
  }
}
```

要点：

- 用 `$CLAUDE_PROJECT_DIR`（Claude Code 注入的项目根环境变量）而非相对路径，避免从子目录启动时找不到脚本。
- 显式 `bash` 前缀，不依赖脚本可执行位（跨平台更稳，Windows/WSL 少踩坑）。
- `PreToolUse`/`PostToolUse` 才用 `matcher`；这三类事件**不识别 matcher**，写了也无效。
- 钩子默认 60 秒超时。`UserPromptSubmit` 若挂了 HTTP 调用要确保快返回，或把重活异步化。

放好后 `claude` 重启会话，`/hooks` 应能看到三个事件。然后跑方法 1 的管道逐个验证，最后做端到端测试：输一句带「测试」的提问，Ctrl+R 看注入。

## 七、与 `-p` 无头模式联动：批量任务的记忆管道

`-p`（print/headless）模式适合 CI、批处理、脚本编排。但 `-p` 每次都是「冷启动」，**上下文天然为空**——这正是 `SessionStart` 钩子的用武之地。

给一个 `-p` 审查任务接上记忆管道：

```bash
claude -p "按 STATUS.md 的目标，审查 src/ 的错误处理是否统一返回 Result 类型" \
  --output-format json | jq -r '.result'
```

这条命令运行时：

1. `SessionStart` 触发 → 注入 STATUS.md 目标 + git 摘要 + 上次抢救的笔记。Claude 一上来就知道「目标是统一 Result 类型」。
2. 若审查涉及测试，`UserPromptSubmit` 命中关键词 → 补 testing.md 规范（注：`-p` 的 prompt 也会过 `UserPromptSubmit`）。
3. 若上下文将满自动压缩 → `PreCompact` 抢救，下次 `-p` 还能读到。

进一步，把多个 `-p` 串成批量管道，每个任务都自带项目记忆：

```bash
# 批量审查最近 5 个改动文件，各自独立但共享记忆管家
for f in $(git diff --name-only HEAD~5); do
  claude -p "审查 $f 的错误处理与测试覆盖，违反 STATUS.md 约束请指出" \
    --output-format json | jq -r '.result' > "review-$(basename $f).md"
done
```

每个 `-p` 冷启动都被 `SessionStart` 喂了相同的项目上下文，输出一致、可复现。这比依赖会话历史稳定得多——**记忆不在脑子里，在磁盘和钩子里**。

> 组合心法：`SessionStart` 管「冷启动缺上下文」，`UserPromptSubmit` 管「提问缺相关文档」，`PreCompact` 管「长任务丢目标」。三者覆盖了 `-p` 批处理和长交互会话的全部记忆盲区。

## 实战案例：端到端跑通「项目记忆管家」

把上面的零件组装成一个可复现的最小演示。

**步骤 1**：建项目骨架。

```bash
mkdir mem-butler-demo && cd mem-butler-demo
git init -q
mkdir -p .claude/{hooks,context,notes}
touch STATUS.md
```

**步骤 2**：填 `STATUS.md` 和一个知识片段。

```bash
cat > STATUS.md <<'EOF'
# 目标
- 把 login() 改成返回 Result<User, AuthError>
# 约束
- 不引入新依赖
EOF

cat > .claude/context/testing.md <<'EOF'
# 测试规范
- 用 Vitest，文件 *.test.ts
- 禁止 mock 数据库，用 testcontainers
- 覆盖率核心模块 ≥ 90%
EOF
```

**步骤 3**：写入第三~五节的三份脚本，`chmod +x .claude/hooks/*.sh`（虽然用 `bash` 显式调用不强制，但加上无害）。

**步骤 4**：写入第六节的 `settings.json`。

**步骤 5**：单独验证三脚本（方法 1）。

```bash
echo '{"hook_event_name":"SessionStart","source":"startup","cwd":"'"$PWD"'","session_id":"d1","transcript_path":""}' \
  | bash .claude/hooks/on-session-start.sh
# 期望：输出含 STATUS.md 目标 + Git 摘要

echo '{"hook_event_name":"UserPromptSubmit","prompt":"给 login 补单测","cwd":"'"$PWD"'","session_id":"d1","transcript_path":""}' \
  | bash .claude/hooks/on-prompt-submit.sh
# 期望：输出 JSON，additionalContext 含 testing.md 规范
```

**步骤 6**：端到端。启动 `claude`，`/hooks` 确认三个事件，输入「给 login 补单测」，Ctrl+R 看注入。再 `/compact`，检查 `.claude/notes/` 是否生成了抢救笔记。重启会话，确认 `SessionStart` 把抢救笔记读回。

**步骤 7（批量）**：跑第七节的 `-p` 审查循环，观察每个任务都被喂了 STATUS.md。

跑通后，这套管家可直接复制到任何真实项目：改 `STATUS.md`、增减 `.claude/context/*.md`、调整 `on-prompt-submit.sh` 的关键词路由表即可。

## 工作流程图

```mermaid
flowchart TD
    A[会话启动 / 恢复 / 清屏 / 压缩后] -->|SessionStart 钩子| B[注入: STATUS + git + 上次抢救笔记]
    B --> C[Claude 主循环]
    U[用户或 -p 提交提问] -->|UserPromptSubmit 钩子| D[关键词路由: 补 testing/deploy/db 片段]
    D --> C
    C -->|上下文将满| E["手动 /compact 或自动压缩"]
    E -->|PreCompact 钩子| F["抢救目标/待办 → .claude/notes/"]
    F --> G[压缩执行: 丢弃中间细节]
    G --> C
    F -.磁盘中转.-> A
    C -->|任务完成| H[Stop]
    style B fill:#e8f5e9
    style D fill:#fff3e0
    style F fill:#fce4ec
```

闭环的核心是 `PreCompact → 磁盘 → SessionStart` 这条虚线：压缩丢的信息，靠磁盘中转跨压缩存活。

## 常见问题汇总

- **stdout 污染上下文**：`SessionStart`/`UserPromptSubmit` 的 stdout 全进上下文。调试一律 `>&2`，发布前 `grep -n 'echo'` 扫一遍。
- **`jq` 报 parse error**：stdin 为空或非 JSON。脚本开头 `input=$(cat)` 后加 `printf '%s' "$input" | jq -e . >/dev/null || exit 0` 容错。
- **钩子没生效**：确认 `settings.json` 在 `.claude/` 下、JSON 合法（`jq . .claude/settings.json` 能解析）、`$CLAUDE_PROJECT_DIR` 指向正确。`/hooks` 是第一排查点。
- **`UserPromptSubmit` 误阻断提问**：脚本返回非零 exit 会擦除提问。用 `set +e` 或把可能失败的命令包进 `if`/`|| true`。
- **跨平台路径**：Windows 下用 Git Bash 或 WSL 跑这些 `.sh`；或改写为 `.js`/`.py` 脚本，`command` 用 `node`/`python` 前缀。
- **钩子拖慢响应**：`UserPromptSubmit` 每条提问都跑，脚本必须快（< 200ms）。重活（调 API、跑 lint）留给 `PostToolUse` 或 `-p` 的独立步骤。

## 延伸阅读

- 官方 Hooks 参考（事件、JSON I/O、exit code 语义）：`https://code.claude.com/docs/en/hooks`
- 官方 Hooks 入门指南（配置与示例）：`https://code.claude.com/docs/en/hooks-guide`
- Hooks 输入输出 Schema 权威参考（FrancisBourre gist）：`https://gist.github.com/FrancisBourre/50dca37124ecc43eaf08328cdcccdb34`
- DeepWiki · 会话与上下文钩子详解：`https://deepwiki.com/disler/claude-code-hooks-mastery/3.2-session-and-context-hooks`
- aiorg · Claude Code Hooks 完整指南（20+ 可复制配置）：`https://aiorg.dev/blog/claude-code-hooks`

## 训练营适用性评估

- **适合类型**：Claude Code 进阶训练营、DevOps 自动化训练营、AI 编程工程化实战营。要求学员已跑通首个项目，能读 bash 和 jq。
- **预计教学时长**：3~4 小时（含动手）。讲解钩子机制 45 分钟，三脚本逐段精讲 90 分钟，端到端调试 60 分钟，`-p` 批量联动 30 分钟。
- **难度**：中。难点不在单脚本，而在「stdout 注入」「exit code 2 差异化语义」「transcript 解析」「闭环设计」四个概念的串联。
- **延伸作业**：把关键词路由升级为本地嵌入语义检索；给 `PreCompact` 增加决策抽取（不只是用户消息，还抽 Claude 的关键承诺）；把管家打包成可 `claude plugin` 安装的技能分发。

## 参考来源

1. [Claude Code Hooks 官方参考](https://code.claude.com/docs/en/hooks) — 8 类钩子事件的输入输出 schema、exit code 语义、matcher 规则的权威定义
2. [Automate actions with hooks 官方指南](https://code.claude.com/docs/en/hooks-guide) — SessionStart/UserPromptSubmit/PreCompact 的配置示例与 stdout 注入上下文机制
3. [Claude Code Hooks — Input & Output Schemas (FrancisBourre gist)](https://gist.github.com/FrancisBourre/50dca37124ecc43eaf08328cdcccdb34) — 逐事件的 JSON 输入输出与 exit-code-2 行为矩阵，实现级参考
4. [Session and Context Hooks (DeepWiki)](https://deepwiki.com/disler/claude-code-hooks-mastery/3.2-session-and-context-hooks) — 会话与上下文生命周期钩子的系统化讲解
5. [Claude Code Hooks Complete Guide (aiorg, 2026)](https://aiorg.dev/blog/claude-code-hooks) — 20+ 可复制配置，含 SessionStart/PreCompact 的生产级用法

> [!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)
