对话越长,Claude 越笨?我从源码里找到了真相
系列:比别人多看一层(第二篇)
用 Claude Code 写代码,有个规律很多人都发现了:
刚开始用,又快又准。
对话一长,开始犯迷糊。让它改 A,它改了 B。说过的规范,它忘了。
你以为是模型变笨了。
其实是上下文窗口快撑不住了。
但真正的问题不是”窗口有多大”,而是——你以为的 200K,根本没有 200K 可以用。
我翻了 Claude Code 的源码,把这套上下文管理机制从头到尾拆了一遍。
你以为有 200K,实际能用的只有 167K
Claude Code 的默认上下文窗口是 20 万 token。
但源码里有一套精密的”缓冲区预留”机制,会在你不知情的情况下,把可用空间提前锁掉一大块。
src/utils/context.ts:
exportconstCOMPACT_MAX_OUTPUT_TOKENS = 20_000// 为压缩摘要预留 20KexportconstAUTOCOMPACT_BUFFER_TOKENS = 13_000// 自动压缩的安全缓冲 13K
计算一下:
200K(总窗口)- 20K(为压缩摘要预留的输出空间)- 13K(自动压缩的安全缓冲)= 167K(实际触发自动压缩的阈值)
你填满 167K,Claude Code 就会自动压缩对话历史——不会提前问你。
这个 20K 的预留,来自 Anthropic 工程师的真实生产数据:
// Based on p99.99 of compact summary output being 17,387 tokens.// 基于生产环境 p99.99 的压缩摘要输出为 17,387 tokenconstMAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000
他们测了真实用户的压缩摘要,99.99% 不超过 17,387 token,所以预留了 20K 作为上限。这是真实数据,不是拍脑袋。
自动压缩发生时,早期对话会被”改写”
当上下文到达 167K,Claude Code 触发 auto-compact。
它会把整段对话历史压缩成一份摘要,然后用这份摘要替换原来的历史。
这个摘要最多 20K token。
也就是说,一段可能几万字的对话历史,被压缩进了 20K 的摘要里。
细节丢了,上下文丢了,你在对话开头说的那些”这个项目的背景是……”,大概率不完整了。
这不是 bug,是设计。 系统的目标是让对话能继续,而不是保留所有细节。
有个警告线,但很多人没注意到
src/utils/contextSuggestions.ts:
constNEAR_CAPACITY_PERCENT = 80// 达到 80% 时触发警告
当上下文使用率到 80%,Claude Code 会在界面上给出提示。
但这个提示很容易被忽略——它不是弹窗,不会打断你的工作流,只是状态栏里一行字。
更重要的是,同一个文件里还定义了几个”浪费检测”阈值:
constLARGE_TOOL_RESULT_PERCENT = 15// 某个工具输出超过总上下文 15%,报警constREAD_BLOAT_PERCENT = 5// Read 工具结果超过总上下文 5%,报警constMEMORY_HIGH_PERCENT = 5// memory 文件占比超 5% 且超 5K token,建议裁减
这些阈值背后,是系统对”什么操作最浪费上下文”的判断。
最浪费上下文的三个操作,源码直接告诉你
src/utils/contextSuggestions.ts 里,系统对不同工具的”可节省 token 比例”有明确估算:
caseBASH_TOOL_NAME:savingsTokens: Math.floor(tokens * 0.5)// Bash 工具输出:预估可节省 50%caseFILE_READ_TOOL_NAME:savingsTokens: Math.floor(tokens * 0.3)// 文件读取:预估可节省 30%caseWEB_FETCH_TOOL_NAME:savingsTokens: Math.floor(tokens * 0.4)// 网页抓取:预估可节省 40%
Bash 工具被认为是最浪费的,系统认为它的输出有一半是可以省掉的。
源码里还附了建议:
Pipe output through head, tail, or grep to reduce result size.Avoid cat on large files — use Read with offset/limit instead.// 用 head/tail/grep 过滤输出,减少结果体积// 避免对大文件用 cat,改用带 offset/limit 参数的 Read 工具
cat 一个大文件,是吃上下文最快的操作之一。
另一个隐形杀手:反复读同一个文件
src/utils/contextAnalysis.ts:
// Calculate duplicate file reads// 计算重复读取文件造成的 token 浪费if (data.count > 1) {const duplicateTokens = averageTokensPerRead * (data.count - 1) stats.duplicateFileReads.set(path, { count: data.count, tokens: duplicateTokens })}
系统会追踪同一个文件被读了多少次,累计”重复读”浪费的 token。
每次 Read 工具读取一个文件,读取结果会完整保留在对话历史里。
你让 Claude 改了三次同一个文件,每次改之前它都读一遍——这个文件的内容就在对话历史里存了三份。
这是长对话后上下文快速被填满的主要原因之一,也是很多人没意识到的。
系统提示词本身也要占地方
还有一个很多人忽略的事:Claude Code 的系统提示词不是免费的,它也要占上下文空间。
源码里把系统提示词分成了两段:
exportfunctionsystemPromptSection(name, compute): SystemPromptSection {return { name, compute, cacheBreak: false }// 静态区:工具定义、编程规范等,可跨会话缓存}exportfunctionDANGEROUS_uncachedSystemPromptSection( name, compute, _reason): SystemPromptSection {return { name, compute, cacheBreak: true }// 动态区:当前目录、session 时间、用户语言偏好等,每轮重新计算}
分界线前的内容(工具定义、规范等)可以被 prompt cache 缓存,多次会话共享,API 费用更低。
分界线后的内容(当前目录、时间、你的 CLAUDE.md 里的规则)每轮都要重新计算,每次都会打破缓存。
这意味着:每次你修改 CLAUDE.md,下一次请求会比平时贵——因为缓存失效了,所有动态内容要重新计算。
那 1M 上下文呢?不是所有人都有
源码里关于 1M 上下文的开启逻辑:
exportfunctionmodelSupports1M(model: string): boolean {const canonical = getCanonicalName(model)return canonical.includes('claude-sonnet-4') || canonical.includes('opus-4-6')// 只有 Sonnet 4 和 Opus 4.6 支持 1M}exportfunctiongetSonnet1mExpTreatmentEnabled(model: string): boolean {returngetGlobalConfig().clientDataCache?.['coral_reef_sonnet'] === 'true'// 通过 A/B 实验(coral_reef_sonnet)逐步开放}
1M 上下文不是对所有人默认开放的,需要:
- 使用 Sonnet 4 或 Opus 4.6
- 模型名带
[1m]后缀,或通过 A/B 实验分配到实验组 - 企业环境下没有被管理员用
CLAUDE_CODE_DISABLE_1M_CONTEXT关掉(HIPAA 合规场景会强制关闭)
同一个模型,不同账号可能上下文容量不一样,原因就在这里。
几个能直接用的结论
1. 新任务开新会话,不要在一个对话里做多件事。
对话历史越长,上下文消耗越快,自动压缩触发越早,细节丢失越多。重要的项目背景写进 CLAUDE.md,而不是依赖对话历史。
2. 不要 cat 大文件,用带参数的 Read。
让 Claude 读文件时,告诉它你需要哪部分。Read offset=100 limit=50 比 cat 整个文件省几十倍 token。
3. 注意重复读同一个文件。
如果你在反复修改一个大文件,每次修改前让 Claude 先读,读取结果会在对话历史里堆叠。必要时用 /compact 手动压缩一次。
4. 改 CLAUDE.md 有成本,不要频繁改。
每次修改 CLAUDE.md,都会打破 prompt cache,下一次请求的 API 费用会更高。把规则写稳定,而不是随时调整。
5. 看到 80% 的警告,立刻 /compact。
不要等系统自动压缩,手动 /compact 的缓冲区只需要 3K,比自动压缩的 13K 缓冲节省了 10K 可用空间。
最后
200K 上下文,听起来很大。
但系统提示词要占一块,工具调用结果要占一块,对话历史要占一块,自动压缩的缓冲区要占一块。
真正留给”有效工作”的空间,比你想的少得多。
上下文不是无限的水缸,是一块有限的白板。
每次工具调用、每次文件读取、每次 Claude 的回复,都在往上面写字。
写满了,就要擦掉重写——擦掉的那部分,就永远消失了。
下一篇:Claude Code 凭什么决定哪些操作直接帮你执行、哪些要弹窗问你——背后是 6 种权限模式、28 个危险命令黑名单、加上 Anthropic 服务端的远程开关。
夜雨聆风