1. 从一次 prompt-too-long 报错说起:Claude Code 记忆管理与压缩到底怎么跑
如果你在本地跑 Claude Code 跑得比较久,大概率见过这个报错:prompt is too long: 210xxx tokens > 200000 maximum。很多人第一反应是「上下文满了,清一下会话」,但清完发现刚聊过的文件路径、刚定的方案全没了,又得重新喂一遍。问题不在「清不清」,而在于你没搞懂 Claude Code 的记忆到底分几层、压缩到底在什么时机触发、压缩后哪些信息被保留、哪些被丢掉。
这篇不聊玄学,直接从 settings 配置一路拆到 API 通道链路,把记忆存储、压缩策略、触发阈值、feature gate 开关全部落到可复制的配置和可验证的步骤上。核心检索词就三个:Claude Code 记忆管理、上下文压缩触发条件、settings 配置链路。适合谁看?适合已经在用 Claude Code 做日常编码、被上下文窗口反复打断、想搞清楚「为什么我的 CLAUDE.md 有时候生效有时候不生效」的开发者。读完你能自己复现压缩前后差异,也能判断网上那些「五层记忆管理」的说法哪些是编的。
先说结论:Claude Code 的记忆不是五层,是三种存储形态加七种压缩策略,但主循环真正调用的只有三种压缩。这个差异是后面所有配置和排障的基础。我试过把会话跑到 90% 以上再观察,压缩行为跟源码里写的阈值完全对得上,下面一步步来。
2. TaoToken 前置:把 API 通道和 settings 链路先接对
在拆压缩之前,得先把请求通道理顺,否则你连usage.input_tokens都拿不到,压缩阈值根本无从观察。Claude Code 的请求最终走的是 Anthropic 兼容的 messages 接口,本地 settings 决定模型、Base URL、Key 从哪来。如果你用的是 TaoToken 这类兼容通道,配置入口在 https://taotoken.net/api ,Key 在控制台生成,模型 ID 直接填 Claude 系列即可。
这里有个容易踩的坑:很多人把 Base URL 写成带/v1或不带/v1混着来,结果 401 和 404 交替出现。正确做法是 Base URL 只写到域名根,路径由客户端自己拼。下面这段是~/.claude/settings.json里跟通道相关的部分,路径和字段名保持和官方一致,你可以直接对照改:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5", "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192" }, "permissions": { "allow": ["Read", "Edit", "Bash(git:*)"] } }注意ANTHROPIC_MODEL这个字段,它决定了context_window_for_model()返回多大的窗口。源码里压缩阈值是按「已用 token / 窗口大小」算的,模型填错会导致阈值判断整体偏移。比如你实际用的是 200k 窗口的模型,却填了个 100k 的 ID,那 90% 阈值会在真实用量 45% 时就触发,压缩来得莫名其妙。
Key 的获取路径是控制台里的 API Keys 页面,生成后只显示一次,记得存好。如果你还没配好,先去 https://taotoken.net/api-keys 拿 Key,再回来继续。通道通了之后,用一条最小请求验证:
curl https://taotoken.net/api/v1/messages \ -H "x-api-key: sk-你的Key" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 64, "messages": [{"role": "user", "content": "reply with ok"}] }'返回里带usage.input_tokens就说明通道没问题,这个字段就是后面观察压缩的抓手。如果返回 401,先查 Key 有没有多余空格;如果返回local proxy failed,多半是 Base URL 写错或本地网络层拦截,跟压缩无关,先把通道修好再谈记忆管理。
3. 可复制配置:settings 里的记忆与压缩相关字段
这一节是全文最该抄的部分。Claude Code 的记忆存储分三种:常驻的CLAUDE.md(热记忆)、按需加载的话题文件(温记忆)、会话转录 JSONL(冷记忆)。压缩策略有七种,但主循环实际只用三种。你要控制的,就是「哪些记忆进上下文」和「压缩在什么阈值触发」。
先看记忆相关的目录结构,这是温记忆的落盘位置:
# ~/.claude/projects/<项目哈希>/memory/ 目录结构示意 # Memory.md —— 索引文件,保存所有话题文件的元信息 # user_profile.md —— 用户背景与构建偏好 # skill_development.md —— 项目下的 skill 信息热记忆CLAUDE.md分两级:用户级~/.claude/CLAUDE.md和项目级./CLAUDE.md。它被注入上下文的方式是作为 System Prompt 之后的一条 user message,不是塞进 system 里。这点很关键,因为压缩时第一条消息(system/bootstrap)会被snip_compact保留,而CLAUDE.md作为紧随其后的 user message,在激进压缩下是有可能被卷进摘要的。想让某条约束绝对不被压掉,写进 system 级别的配置,别只写CLAUDE.md。
压缩行为的开关在环境变量里,源码注释写得很清楚:enabled via CLAURST_FEATURE_REACTIVE_COMPACT=1。也就是说 reactive 分支默认不开,开了之后压缩时机从「只在 end_turn / tool_use 后」变成「每轮模型返回后都判断」。配置片段:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5", "CLAURST_FEATURE_REACTIVE_COMPACT": "1", "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192" } }三件套对照表,配通道时逐项核对:
| 配置项 | 作用 | 常见错误值 | 正确示例 |
|---|---|---|---|
| Base URL | 请求入口 | 带/v1后缀 | https://taotoken.net/api |
| API Key | 鉴权 | 带引号或空格 | sk-xxxx |
| Model ID | 决定窗口大小 | 填错窗口的模型 | claude-sonnet-4-5 |
阈值这块,源码里MicroCompactConfig的默认值是trigger_threshold: 0.75、keep_recent_messages: 10、summary_target_tokens: 2048。而主循环里 auto compact 的触发是 90%,context collapse 是 97%。这几个数字要记住,后面验证时全靠它们对号入座。KEEP_RECENT_MESSAGES = 10意味着无论哪种压缩,最近 10 条消息原样保留,压缩的是更早的 head 部分。
4. 验证请求与成功结果:观察压缩前后差异
配置改完,怎么确认压缩真的按预期跑了?最直接的办法是让会话涨到阈值附近,然后看日志和消息条数变化。源码里压缩成功会打info!日志,比如MicroCompact complete、Auto-compact complete、Context-collapse complete,带上original和compacted的消息数。
先跑一个能快速堆 token 的会话。用 Bash 工具连续读几个大文件,或者直接让模型总结长文本,把usage.input_tokens顶上去。观察点有两个:一是TokenWarning事件,80% 触发 Warning,95% 触发 Critical;二是压缩日志。下面是一段模拟观察流程:
# 启动 Claude Code 时打开调试日志 CLAUDE_CODE_DEBUG=1 claude # 在会话里连续执行,把上下文顶到 90% 以上 # 读几个大文件 Read src/main.rs Read src/lib.rs Read Cargo.toml # 然后问一个需要长回答的问题,让 input_tokens 涨起来当input_tokens超过窗口的 90%,如果没开 reactive gate,你会在end_turn或tool_use之后看到Auto-compact complete,日志里original_count明显大于new_count。开了 reactive gate 的话,每轮返回后都会判断,超过 90% 走reactive_compact,超过 97% 走context_collapse。
压缩后的消息结构长这样:第一条是 synthetic user message,内容是This session is being continued from a previous conversation that ran out of context...加上摘要,后面接最近 10 条原始消息。你可以通过打印消息数组长度来验证:
# 伪代码:验证压缩后消息结构 # 压缩前 messages 长度 = 42 # 压缩后 messages 长度 = 1 (summary) + 10 (recent) = 11 # 且 messages[0].role == "user",内容是 summary preamblereactive_compact比 auto 多两步:先strip_images删掉图片 block,再尝试把最近修改的文件重新注入,最多 5 个文件、单个超过 50KB 跳过。所以如果你在会话里改过文件,reactive 压缩后可能会看到额外的<file path="...">内容块,这是正常的补上下文行为,不是 bug。
context_collapse最激进,它把整个会话压成最多 500 词的紧急摘要,然后只保留摘要加最后一条 user message。触发条件是 97% 以上,属于「再不压下一轮请求就发不出去」的兜底。如果你看到日志里出现EMERGENCY CONTEXT COLLAPSE,说明会话已经到极限了,这时候历史基本只剩骨架。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
压缩相关的报错和通道相关的报错经常混在一起,得分开看。下面按真实报错逐条对。
401 Unauthorized出现在压缩日志之前,说明请求根本没发出去,跟记忆管理无关。检查ANTHROPIC_API_KEY是否有多余空格、是否过期。如果 Key 是对的还 401,看 Base URL 是不是写成了https://taotoken.net/api/v1,多写的/v1会导致路径拼成/api/v1/v1/messages。
local proxy failed通常是本地网络层或 Base URL 解析问题。先确认ANTHROPIC_BASE_URL只写到https://taotoken.net/api,再用第 2 节的 curl 单独验证通道。通道通了,这个错就消失。
reading choices这类报错一般出现在流式响应解析阶段,源码里StreamAccumulator处理MessageDelta时如果stop_reason字段缺失或格式不对会抛。如果你用的是兼容通道,确认返回的stop_reason是end_turn/tool_use/max_tokens这几个标准值之一。非标准值会被当成end_turn处理,但解析层可能先报错。
OAuth相关报错多见于用订阅账号登录而非 API Key 的场景。如果你走的是 API Key 通道,settings 里不要同时留 OAuth 的 token 字段,两者会打架。清掉~/.claude/下的凭据缓存再重启。
还有一个隐蔽的坑:压缩后模型「失忆」。这不是报错,但表现是模型忘了之前定的方案。原因是summarise_head在压缩 head 时,对结构化 blocks 的 tool result 会用占位符[complex content]代替,不展开。如果你的关键信息藏在结构化返回里,压缩后就丢了。解决办法是把关键约束写进CLAUDE.md或 system 级别配置,别依赖工具返回。
排查顺序建议:先 curl 验通道,再看usage.input_tokens有没有正常返回,最后才看压缩日志。通道不通,压缩逻辑根本不会执行。
6. 语义一致 CTA:把配置链路跑通之后
通道和压缩都验证过之后,日常编码场景建议直接上 Coding Plan,省得每次手动配 Key 和模型。入口在 https://taotoken.net/coding-plan ,适合长期跑 Agent 和批量改代码的用法。如果你只是想先验证某个模型在压缩后的表现,用模型对话页面快速试:https://taotoken.net/chat 。接入文档在 https://taotoken.net/doc ,里面有各客户端的 Base URL 和字段对照,配 settings 时对着抄就行。
最后留一个实用习惯:每次改完 settings,先用一条最小请求确认usage.input_tokens正常返回,再开长会话。压缩阈值是按这个字段算的,它不对,后面全是白忙。