深度解析 Claude Code 源码(二):上下文管理与消息压缩机制 1. 长对话为什么会突然“失忆”从一次 413 报错说起如果你用 Claude Code 跑过稍微复杂点的重构任务大概率见过这样的场景前 20 轮对话它还记得你项目里那个UserService的字段命名到第 40 轮你让它接着改它却像换了个人问你“请问 UserService 在哪个文件”。这不是模型变笨了而是上下文窗口Context Window被撑爆后系统触发了消息压缩机制把早期对话“折叠”掉了。Claude Code 的上下文管理是一套分层防御体系核心目标只有一个在 200K tokens 的窗口里尽可能让模型“记得住关键信息”同时不因为超限直接报错退出。它把压缩分成两大层——预防层在调用 API 之前就动手反应层在 API 返回prompt_too_longHTTP 413之后紧急补救。设计哲学是从轻到重、从快到慢、从局部到全局能不调 API 就不调能局部处理就不整体处理。这篇文章聚焦源码实现拆解 token 预算怎么分配、历史消息怎么裁剪、摘要压缩在什么条件下触发。我会给出可复制的配置片段和验证动作让你在本地跑 Claude Code 时能亲眼看到压缩日志对照源码定位关键函数验证压缩前后上下文长度的变化。适合已经用过 Claude Code、想搞清楚它内部机制的开发者也适合正在自研 Agent 框架、需要借鉴上下文管理思路的人。先明确一个概念这里的“压缩”不是把文本用 gzip 压小而是减少发给模型的 token 数量。手段包括把大工具结果存磁盘只留预览、直接砍掉老消息、清空旧工具调用内容、把旧消息段折叠成摘要、以及调用模型生成全量摘要。每一层的“重量”不同触发条件也不同。2. TaoToken 前置准备让 Claude Code 稳定跑起来观察压缩行为要观察压缩日志前提是 Claude Code 能正常发请求。Claude Code 默认走 Anthropic 官方端点但在国内网络环境下直连经常超时而且官方按量计费对频繁调试不太友好。我的做法是把它接到兼容 Anthropic 协议的网关服务上TaoToken 就是这类服务它提供 Anthropic 兼容的/v1/messages接口Claude Code 不用改代码只改环境变量就能指向它。这一步不是必须的如果你已经有可用的 Anthropic Key可以跳过直接看第 3 节。但如果你想像我一样反复触发压缩、观察日志用按量计费的官方 Key 调试成本会很高接一个兼容网关更划算。先说清楚 TaoToken 是什么它是一个大模型 API 聚合网关对外暴露 Anthropic 兼容接口和 OpenAI 兼容接口Claude Code、Cline、Codex 这类工具都能接。它本身不是模型也不改变 Claude Code 的压缩逻辑——压缩逻辑在 Claude Code 客户端源码里网关只负责转发请求。这点要分清楚否则你会误以为换个网关就能改压缩行为。接入前你需要准备三样东西我称之为“三件套”Base URLhttps://taotoken.net/api这是 Anthropic 兼容端点的根路径API Key在控制台创建形如sk-开头的一串字符Model ID比如claude-sonnet-4-5-20250929或claude-opus-4-1-20250805要和你实际想调的模型对应获取 Key 的入口在控制台的 API Keys 页面创建后复制保存页面关闭后不再完整显示。模型对话页面可以用来单独测试某个 Model ID 是否可用避免在 Claude Code 里反复试错。如果你打算长期用 Claude Code 做编码和 Agent 任务Coding Plan 套餐比按量计费更适合高频调试场景。这里要提醒一点Claude Code 的压缩机制和网关无关但网关的上下文长度上限会影响你能不能观察到压缩。如果网关侧对单次请求的 token 数限制比 Claude Code 估算的更严你可能会先撞上网关的 413而不是 Claude Code 自己的压缩触发。所以调试压缩时尽量选上下文上限足够大的模型。3. 可复制配置把 Claude Code 指向兼容端点并打开压缩日志Claude Code 读取配置的方式有好几种最直接的是环境变量。下面这套配置我实测可用你可以直接复制到~/.zshrc或~/.bashrc里。注意 Base URL 用https://taotoken.net/api不要带末尾斜杠也不要加 UTM 参数否则某些版本的 SDK 会拼接出双斜杠路径导致 404。# ~/.zshrc 或 ~/.bashrc export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的Key export ANTHROPIC_MODELclaude-sonnet-4-5-20250929 # 打开调试日志观察压缩触发 export ANTHROPIC_LOGdebug export DEBUGclaude-code:*如果你用的是 Claude Code 的 settings 文件方式可以在项目根目录或用户目录建.claude/settings.json内容如下。这种方式的优先级高于环境变量适合团队统一配置{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-5-20250929, ANTHROPIC_LOG: debug }, permissions: { allow: [Read, Edit, Bash(git:*)] } }配置写完后先别急着跑长对话用一条最简单的请求验证连通性。Claude Code 提供了非交互模式可以直接发一句 promptclaude -p 回复 OK 两个字母即可 --model claude-sonnet-4-5-20250929如果返回OK说明 Base URL、Key、Model ID 三件套都对。如果报 401检查 Key 是否复制完整如果报local proxy failed或连接超时检查 Base URL 是否写成了https://taotoken.net/api/多了斜杠或者误加了其他路径。接下来是关键一步制造一个会触发压缩的长对话。最省事的办法是让 Claude Code 反复读一个大文件。你可以准备一个几千行的日志文件然后连续让它读# 生成一个约 8000 行的测试文件 seq 1 8000 | awk {print line $1 : some log content here} /tmp/big.log # 启动 Claude Code 交互模式 claude进入交互后连续输入类似“读取 /tmp/big.log 的前 2000 行并总结”这样的指令重复十几次。每次工具调用都会往消息历史里塞入一大段tool_result上下文会快速膨胀。这时候你就能在终端里看到压缩相关的日志了。关于日志Claude Code 的压缩触发点会打印类似applyToolResultBudget、microcompact、autocompact的关键字。如果你用的是DEBUGclaude-code:*日志会比较杂建议配合grep过滤claude 21 | tee /tmp/cc.log # 另开一个终端 tail -f /tmp/cc.log | grep -E compact|budget|collapse|prompt_too_long这样你能实时看到哪一层被触发了。我试过连续读 15 次大文件大概在第 8 次左右看到applyToolResultBudget先触发把单条超大结果存到了/tmp下的临时文件消息里只留了前 2000 字节的预览。4. 验证请求与成功结果对照源码看压缩前后上下文长度变化配置跑通后重点来了怎么确认压缩真的发生了以及压缩前后上下文长度变了多少。Claude Code 的源码里上下文管理的核心逻辑嵌在query循环中每一轮 API 调用前会依次执行预防层的五个步骤。你可以对照仓库ChinaSiro/claude-code-sourcemap里的query.ts来定位。先看预防层的执行顺序这是理解压缩的关键// 伪代码对应 query 循环中的预防层 while (true) { // ① 单条工具结果太大 → 存磁盘 messages applyToolResultBudget(messages) // ② 老消息太多 → 直接砍 messages snipCompact(messages) // ③ 旧工具结果 → 清空内容 messages microcompact(messages) // ④ 旧消息组 → 折叠为摘要 messages contextCollapse.projectView(messages) // ⑤ 整体太大 → 调 API 生成全量摘要 messages await autocompact(messages) // 调用模型 for await (const msg of callModel(messages)) { if (isPromptTooLong(msg)) { withheld true // 拦截 413先不吐给 UI } if (!withheld) yield msg } // 反应层API 失败后补救 if (apiReturned413) { await contextCollapse.drain() await reactiveCompact.tryReactiveCompact() continue // 重试 } }每一层都尝试把上下文压到安全范围如果够了后面的层就不触发。这就是“从轻到重”的含义。现在验证压缩前后的长度变化。最直接的办法是在日志里找 token 计数。Claude Code 内部用tokenCountWithEstimation做客户端估算这个估算值会出现在 debug 日志里。你可以在长对话过程中观察这个数字的走势# 过滤出 token 估算相关的日志行 grep -E tokenCount|estimated|context.*window /tmp/cc.log | tail -50正常情况下你会看到 token 数随着对话轮次上升然后在某一轮突然下降——那个下降点就是压缩触发点。下降的幅度取决于触发了哪一层microcompact只清空旧工具内容下降幅度中等autocompact做全量摘要下降幅度最大可能从 180K 直接掉到 30K。再验证一个细节applyToolResultBudget的触发阈值。源码里这个阈值大约是单组tool_result总大小超过 200K 字符。触发后从大到小逐个替换直到总大小降到 200K 以下。替换后的内容长这样persisted-output Output too large (10.5MB). Saved to: /tmp/claude/tool-use-abc123.txt Preview (first 2000 bytes): ...前2000字节... /persisted-output你可以在/tmp下找到这些持久化文件验证它们确实存在。这一步很关键因为它证明了压缩不是“丢弃”而是“外置存储”——模型看不到完整内容了但内容还在磁盘上必要时可以重新读取。contextCollapse的验证稍微复杂一点因为它涉及后台侧链 agent。它的生命周期分四步spawn后台调模型生成摘要、staged摘要进队列缓冲、committed确认安全后提交、projectView读时投影把原消息替换成collapsed占位符。你在 REPL 里看到的还是完整消息但发给 API 的版本已经被替换了。要验证这一点可以在 debug 日志里搜索collapsed关键字对比 REPL 显示和实际请求体。autocompact的触发条件是消息总 token 数 contextWindow - 33000。以 200K 窗口为例大约在 167K tokens 时触发。它是最重的一层会再调一次模型把整个历史压成一条摘要消息。源码里有个标记hasAttemptedReactiveCompact防止反应层无限循环重试。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth调试压缩机制时你大概率会先撞上接入层的报错而不是压缩逻辑本身的问题。我把几个高频错误和对应排查方法列出来都是实际踩过的。401 Unauthorized最常见。原因通常是 Key 没复制完整、Key 已过期、或者 Base URL 和 Key 不匹配比如把 OpenAI 格式的 Key 用在了 Anthropic 端点上。排查方法先用模型对话页面单独测这个 Key 和 Model ID 的组合确认 Key 本身可用再回到 Claude Code 检查环境变量。注意ANTHROPIC_API_KEY和ANTHROPIC_AUTH_TOKEN是两个不同的变量Claude Code 认前者。local proxy failed / connection refused这个报错通常出现在 Base URL 写错的情况下。检查三点一是 URL 是不是https://taotoken.net/api不要带末尾斜杠二是有没有误加 UTM 参数SDK 拼接路径时会把查询参数带进去导致 404三是本地有没有残留的代理配置比如HTTP_PROXY环境变量干扰请求。如果你之前配过其他工具的代理先unset HTTP_PROXY HTTPS_PROXY再试。reading choices 报错这个错误信息通常来自 OpenAI 兼容格式的响应解析说明请求打到了 OpenAI 端点而不是 Anthropic 端点。Claude Code 走的是/v1/messages如果你把 Base URL 配成了 OpenAI 兼容的路径就会解析失败。确认 Base URL 是 Anthropic 兼容端点Model ID 也是 Anthropic 的模型名。OAuth 相关报错Claude Code 某些版本会尝试 OAuth 登录流程如果你用的是 API Key 方式需要在配置里明确禁用 OAuth。检查 settings 里有没有forceLoginMethod之类的字段或者环境变量里有没有残留的 OAuth token。最干净的做法是清空~/.claude下的缓存再重新配置。压缩不触发如果你跑了很久长对话却没看到压缩日志可能是 token 估算没到阈值。检查ANTHROPIC_LOG是否真的生效有些版本需要DEBUG和ANTHROPIC_LOG同时设置。另外如果你用的是小窗口模型比如 32K压缩会更早触发反而更容易观察。压缩后模型“失忆”太严重这是snipCompact或autocompact触发后的正常现象。snipCompact是直接砍掉老消息信息直接丢失autocompact是生成摘要摘要质量取决于模型。如果你发现关键信息丢了可以在对话里主动重申或者把重要上下文写进CLAUDE.md项目文件这样每轮都会带上。排查时有个通用技巧把ANTHROPIC_LOGdebug和DEBUGclaude-code:*都打开日志会记录每一层压缩的触发和 token 变化。对照源码里的函数名搜索日志能快速定位是哪一层在起作用。6. 继续深入把压缩机制用在你自己的 Agent 里Claude Code 的这套分层防御体系本质上是把“上下文管理”从一个大函数拆成了五个职责单一的步骤每个步骤有独立的触发条件和压缩力度。这个设计思路可以直接搬到自研 Agent 里先做最轻的局部清理清空旧工具结果再做中等的裁剪砍老消息最后才动用最重的模型摘要。关键是每一层都要有明确的触发阈值和降级路径避免一上来就调模型做摘要那样成本和延迟都不可控。如果你想继续验证可以试着改一下源码里的阈值参数比如把applyToolResultBudget的 200K 字符限制调小到 50K观察压缩触发频率的变化。源码仓库在ChinaSiro/claude-code-sourcemapquery.ts是入口。改完后重新跑长对话对比日志里的 token 曲线你会对“预算分配”有更直观的感受。接入层如果还没跑通先去 API Keys 页面把三件套配好再用模型对话页面单独验证 Model ID 可用最后回到 Claude Code 跑长对话观察压缩日志。接入文档里有各工具的完整配置示例Cline、Codex 的接法也在里面。长期做编码和 Agent 任务的话Coding Plan 比按量计费更适合反复调试压缩行为的场景。