Claude Code 的 PreCompact 与 PostCompact 都属于会话生命周期里”context 压缩(compaction)”这一道工序,但一个在压缩动作启动前触发、一个在压缩完成、摘要已生成后触发。把这两个 hook 放在一起看,才能完整覆盖”会话在快要撑爆 context window 时,会按什么顺序发生什么、哪一步可以被业务脚本拦截或补充信息”。
一、context 压缩全流程中的两个触发位
Claude Code 在检测到上下文接近模型窗口上限、或用户手动调用 /compact 时,会把会话历史做摘要化压缩。整个压缩管线依次经过四步:
- 读取当前会话记录,估算上下文占用;
- 触发
PreCompacthook(matcher 为auto或manual); - 调用一个只读 agent 生成摘要,补回 plan、CLAUDE.md 等关键信息,并写入 compact_boundary;
- 触发
PostCompacthook。
PreCompact 落在”摘要 prompt 生成前”,这意味着脚本可以读取当前 transcript、注入自定义摘要要求,或在长事务进行中返回 exit code 2 阻断压缩。PostCompact 落在”summary 已经产生、隐藏的摘要 user message 也已经建好之后”,它的标准输出不会进入下一轮模型上下文,只能用于审计、落盘、刷新外部看板等只读动作。
| Hook | 触发环节 | 能否阻断压缩 | 输入关键字段 | 标准输出是否进入下一轮上下文 |
|---|---|---|---|---|
PreCompact |
摘要 prompt 生成前 | 是(exit code 2 或返回 decision: block) |
trigger(auto/manual)、custom_instructions |
否,仅做拦截或写盘 |
PostCompact |
summary 与 compact_boundary 建好之后 | 否 | trigger、compact_summary |
否,仅做审计与通知 |
需要特别区分的两种 auto 触发:主动型是 Claude Code 在 context 撑满前主动压缩,被动型是上一次请求已经返回 context 超限错误,压缩是为恢复会话而做的。阻断前者可直接跳过压缩,阻断后者会让原始错误透传到当前请求。
二、PreCompact 的三类工程实践
把 PreCompact 当作”压缩前的最后一个写入窗口”是它主要的价值。常见有三类落地。
- transcript 备份:在压缩吃掉旧消息前,把当前
.jsonl会话记录复制到归档目录,便于回查被摘要丢掉的关键决策; - 追加摘要要求:在 hook 中把”必须保留的字段清单”通过
custom_instructions回写,让摘要 agent 不漏关键变量名、接口路径、未完成 TODO; - 长事务保护:在涉及长跑事务(部署、迁移、批量回归)时阻断自动压缩,避免事务中间上下文被切走、模型丢失当前进度。
下面这段是 PreCompact 自动模式备份 transcript 的精简配置:
{
"hooks": {
"PreCompact": [
{
"matcher": "auto",
"hooks": [
{
"type": "command",
"command": "cp \"$CLAUDE_TRANSCRIPT_PATH\" \"$HOME/.claude/backups/$(date +%Y%m%d-%H%M%S)-$CLAUDE_SESSION_ID.jsonl\""
}
]
}
]
}
}
配置生效后,Claude Code 在每次自动压缩前都会把当前 transcript 拷贝到归档目录。备份完后再让摘要流程继续即可,不影响会话连贯性。
三、PostCompact 的三类工程实践
PostCompact 拿到的 compact_summary 字段是刚生成的完整摘要,但脚本无法再回头修改会话上下文。所以它的用法集中在”对外”。
- 落盘摘要:把摘要写入团队的会话归档系统或 Notion 周报,作为知识沉淀;
- 审计与告警:在审计敏感会话中,把摘要同步到 SIEM,配合 hookeventname 字段做事件分流;
- 刷新外部状态:把”会话刚被压缩过”这一事件通知到 IM 机器人或状态面板,方便团队了解当前会话经历了多少次压缩。
下面这段是 PostCompact 把摘要写入归档文件的示例:
#!/usr/bin/env bash
# PostCompact handler: archive the fresh summary
input=$(cat)
summary=$(echo "$input" | jq -r '.compact_summary // ""')
trigger=$(echo "$input" | jq -r '.trigger // "auto"')
sid=$(echo "$input" | jq -r '.session_id')
ts=$(date -u +%Y%m%dT%H%M%SZ)
out="$HOME/.claude/summaries/${sid}-${trigger}-${ts}.md"
printf "# Session %s\n\n%s\n" "$sid" "$summary" > "$out"
把这段脚本挂到 PostCompact 的 command hook 上,每次压缩完成都能拿到一份独立的摘要快照,方便事后比对摘要质量与上下文丢失情况。
四、PreCompact 与 PostCompact 的协同模式
两者单独看都只是一段生命周期脚本,但串起来可以形成”压缩前加固 + 压缩后校核”的闭环。
| 协同模式 | PreCompact 职责 | PostCompact 职责 | 适用场景 |
|---|---|---|---|
| 审计闭环 | 备份 transcript | 落盘摘要 | 长期项目的会话复盘 |
| 长会话续命 | 阻断自动压缩 / 注入摘要要求 | 通知 IM 群”刚压缩过” | 跨天多轮调试 |
| 敏感信息治理 | 在压缩前脱敏 transcript | 校验摘要里是否残留密钥 | 合规要求严格的行业 |
实操上更推荐把 PreCompact 的备份目录与 PostCompact 的摘要目录设为同一前缀(例如 ~/.claude/sessions/<sid>/),便于复盘时把”原始 transcript”与”压缩后摘要”配对查看。
五、落地时的两个易错点
第一,PostCompact 的 stdout 不会进入下一轮模型上下文,把它当作 SessionStart 用是常见误区——若希望压缩后向模型补充信息,应改在 SessionStart 的 compact matcher 里返回,否则信息只会被打印给用户。
第二,阻断 PreCompact 只能延缓主动压缩,对于已经触发 context 超限错误的被动压缩,阻断会让请求失败而不是恢复会话。判断方法很直接:PreCompact 输入里 trigger: auto 且紧邻一次 context_length_exceeded 错误时,阻断基本无效,应让它走完。
常见问题(FAQ)
Q1:PreCompact 可以阻止自动压缩吗?
可以。脚本返回 exit code 2 或输出 decision: block 即可阻止主动型自动压缩;被动型压缩会失败。
Q2:PostCompact 的输出会进入下一轮模型上下文吗?
不会。PostCompact 的 stdout 只用于审计与通知,无法影响后续会话。
Q3:两个 hook 的 matcher 怎么区分手动与自动?
matcher 接受 manual(/compact 触发)或 auto(context 撑满触发),需要分别处理时分别声明。