🚀 省流助手
- 现象:在手机上通过cc-connect远程桥接用 Claude Code,长对话里它突然"答非所问"------不是变水,而是凭空冒出我没说过的前提(我说"一个 git 仓",它非要论证"两个 git 仓怎么隔离")。
- 关键反差 :我平时一直用 1M 上下文 ,本不该撞墙;但通过 IM 桥接拉起的会话,悄悄回落成了 200k 默认------不是我以为继承的 1M。坑就在这层"配置预期错位"。
- 根因 :jsonl 日志实锤------出错那刻占用 ≈177k(200k 窗口的 88%) ,全程贴着 200k 天花板跑。逼近上限触发了"虚构"(confabulation),所以飘。不是工具 bug。
- 解决 :把上下文切回 1M ,回答当场恢复正常;长对话用一行
jq监控占用,逼近 80% 就开新会话/精简。
一、现象:它不是答得差,是答得"不在一个频道"
先说清楚,这次的毛病和大家常吐槽的"AI 降智"不一样。
降智是:答案变水、变浅、变笼统,但还在聊同一件事 。而这次是另一种病------答非所问、驴唇不对马嘴。我管它叫"飘"。
具体场景:我在跟 Claude Code 讨论一个方案,核心设定从头到尾就一句话------"把所有邮件文件放进一个 git 仓,再用软链接打到各个目录"。聊了十几轮后,它突然来了这么一段(原文摘录):
结论:物理上确实是两个独立的 git 仓(项目 git + 邮箱 git),这是"邮件不能进业务仓"的必然结果......
我当场懵了:哪来的第二个 git?从头到尾都是一个 git。 它凭空造了一个我没提过的前提,还煞有介事地论证"两个 git 怎么隔离、会话怎么不背认知负担"。
这里有个细节,也是我一开始根本没往"上下文满了"上想 的原因:我平时用 Claude Code 都是开着 1M 上下文的,这点长度的对话连窗口的零头都占不到,从没撞过墙。所以第一反应是"工具是不是有 bug 串台了",而不是"上下文是不是满了"。真相比这两个猜测都更有意思------也更值得每个用 AI 协作的人记一笔。
二、排查路径:三个假设,逐个验
我没有靠感觉下结论,而是把这次会话的原始日志捞出来对质。Claude Code 每次会话都会在本地存一份 jsonl:
bash
# 会话日志位置(按项目路径编码成目录名)
~/.claude/projects/<your-project>/<session-id>.jsonl
每行一个 JSON 事件,包含 type(user/assistant)、message.content、以及最关键的 message.usage(token 用量)。下面三个假设逐个验。
假设 1:是不是消息桥接工具串了上下文?
我不是在终端裸用 Claude Code,而是通过一个 IM 桥接在手机上跑长对话------很自然先怀疑它把别的对话混进来了。
验证:把"两个 git"这串字第一次出现的位置揪出来,看它来自谁。
bash
jq -r 'select(.type=="user") | (.message.content | if type=="string" then . else (map(select(.type=="text")|.text)|join(" ")) end)' "$F" \
| grep -n "两个\|第二个"
结果:用户消息里"第二个 git"只出现过一次,而且是我事后的否定句 ------"哪来的第二个 git,都是一个 git"。它第一次出现是在 AI 自己的回复 token 里,没有任何外部内容引入。
假设 1 排除:单线程、无注入、无串台。桥接工具只负责传消息,不改内容。
假设 2:是不是触发了自动压缩(compact)?
长对话超长时,有些工具会自动把前文摘要压缩,摘要失真就可能丢主线。
验证:
bash
grep -c "isCompactSummary\|compact_boundary\|\"type\":\"summary\"" "$F"
# 输出:0
假设 2 排除:全程 0 次压缩事件。不是摘要把信息揉丢了。
假设 3:是不是上下文逼近窗口上限了?
这是最后、也最该量化的假设。注意一个关键区分:模型本身(Opus)支持 1M 上下文,但 Claude Code 客户端默认只给会话开 200k 这一档,用满 1M 要显式切到 1M 档位。会不会是聊太长、撞上了这个 200k 默认档的天花板,把我早期说的"一个 git 仓"那几轮"挤"出了有效注意力?
这个假设不能靠猜,得算出出错那一刻的上下文占用。
三、关键证据:一条 token 曲线把真相焊死
上下文占用不是单看 input_tokens,而是三项之和:
ini
ctx = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
一行 jq 把每条回复的占用按时间拉出来,再定位"两个 git"那条:
bash
jq -r 'select(.type=="assistant" and .message.usage)
| .message.usage as $u
| (($u.input_tokens//0)+($u.cache_read_input_tokens//0)+($u.cache_creation_input_tokens//0)) as $ctx
| "\($ctx)\t\(.timestamp[11:19])"' "$F"
把结果和"漂移点 / 我察觉问题 / 切换 1m"几个节点对齐,曲线长这样:
| 事件 | 上下文占用 | 状态 |
|---|---|---|
| "两个 git" 漂移发生 | ≈ 177k | = 200k 的 88%,危险区 |
| 我回怼"哪来的第二个 git" | ≈ 190k | 95% 满 |
| 漂移后最后一条 | 197,435 | 死贴 200k 天花板 |
| 紧接着下一条 | 206,606 | 首次突破 200k |
| 会话尾段 | ≈ 320k | 平滑增长,无回落 |
这张表是整个排查的 turning point。读它:
整个对话------包括漂移点、以及漂移后约 17 分钟------上下文一直死贴在 200k 以下(177k → 190k → 197k),然后才"破墙"冲到 206k,最后一路涨到 32 万。
这是一条教科书式的签名:前半段被 200k 卡着贴边跑,我察觉不对劲后把模型上下文升到 1m,墙一拆,占用立刻越过 200k 往上走。日志的曲线,独立印证了"先撞墙、后升级"的全过程------不是我先入为主,是数字自己对上的。
四、根因:窗口快满时,最先"糊掉"的恰恰是早期锚点
一句话根因:上下文逼近 200k 上限时,模型对"很早之前已确立的事实"的注意力衰减最严重,于是它凭空脑补来填洞------这就是 confabulation(虚构)。
为什么是"飘"而不是"降智",关键就在这里:
- "一个 git 仓 + 软链"这个核心设定,是我在很靠前的几轮说的。
- 窗口贴满时,最该被参照的早期锚点,恰好是有效注意力里最先被稀释的那部分。
- 模型不会"空着"------它会用一个看似合理的前提("两个 git")把洞补上,并自信地往下论证。
降智是均匀变笨(注意力够、但深度不足);飘是局部失锚(早期关键信息掉出有效注意力,被虚构替代)。两者根子不同,处方也不同。
但故事还有更值得记一笔的一层------为什么偏偏是我撞上了。
我平时用 Claude Code 全程开着 1M 上下文 ,这点对话长度连零头都占不满,理论上永远撞不到墙。我一直默认:桥接工具拉起的会话,自然也继承我平时这套 1M 配置。结果它没有。
| 我以为 | 实际 | |
|---|---|---|
| 交互式终端会话 | 1M 上下文 | 1M(对的) |
| 桥接工具拉起的会话 | 以为继承 1M | 悄悄回落成 200k 默认 |
这就是真正的坑:非交互方式(桥接、自动化、定时任务)拉起的 AI 会话,往往不继承你交互式终端里的个性化配置,而是回落到工具的出厂默认值。 这里要分清两层:Opus 模型本身 支持 1M 上下文,但 Claude Code 客户端 默认只给会话开 200k 这一档,用满 1M 要显式切档(在模型 id 上体现为 [1m] 这样的后缀变体)。我平时终端里开着 1M 档,却没想到桥接拉起的会话回落到了 200k 默认档。于是我在"自以为有 1M"的错觉里,一头撞上了 200k 的墙------窗口比我以为的小了 5 倍,撞墙当然猝不及防。
所以这不是"我不会管理上下文/不会压缩",而是"我以为的配置和实际跑的配置错位了"。 这种错位最阴险的地方在于:它不会报错、不会提示,只会让回答悄悄开始飘。
五、解决方案
临时救火
发现答非所问、且确认上下文已经很长时,别跟它继续掰扯------它只会在错误前提上越陷越深。两个动作二选一:
text
1. 直接开新会话,把核心设定重新喂一遍(最干净)
2. 把会话上下文档位从 200k 切到 1m(Opus 模型本身支持 1M,客户端显式切档即可)
我当时选了切 1m。效果立竿见影:同样长度的对话,从占满 200k 变成只用了 1m 的 ~20%,彻底离开危险区,后续回答恢复正常。
永久习惯
把"看一眼 token 占用"做成长对话里的肌肉记忆。一行命令看当前会话最新占用:
bash
F=$(ls -t ~/.claude/projects/<your-project>/*.jsonl | head -1)
jq -r 'select(.type=="assistant" and .message.usage) | .message.usage
| (.input_tokens//0)+(.cache_read_input_tokens//0)+(.cache_creation_input_tokens//0)' "$F" \
| tail -1
占用逼近窗口的 80% 就是黄线------要么开新会话,要么升上下文,别硬撑到撞墙。
六、预防建议
- 别假设"配置会被继承",去核实实际值:这次最大的教训。凡是非交互方式(桥接、自动化脚本、定时任务、CI)拉起的 AI 会话,别默认它继承了你交互式终端里的个性化设置------它很可能回落到出厂默认(如 200k 而非你平时的 1M)。第一时间确认它实际跑的上下文窗口有多大。(这条是基于本次实测 + 日志佐证的工程经验,不同工具/版本的继承行为可能不同------所以重点不是"记住某个默认值",而是"用日志核实实际窗口"这个动作本身。)
- 长对话定期"重锚":关键设定每隔一段主动复述一遍,把它顶回近端上下文,别指望模型一直记得几十轮前的话。
- 重要结论落盘:把定稿的设计/约定写进文件,让模型对着文件改,而不是对着几十轮的对话记忆改------文件是稳定锚点,对话不是。
- 区分"飘"和"降智"再处置:答案变水 → 可能要换更强模型或拆解任务;答非所问/捏造前提 → 先查上下文是不是快满了,对症的是开新会话/升上下文,换模型没用。
七、知识点提炼
① Claude Code 的会话日志在哪、长什么样
每次会话都本地落盘成 jsonl:~/.claude/projects/<编码后的项目路径>/<session-id>.jsonl。每行一个事件,type 区分 user/assistant,message.usage 记录 token 用量。它是排查 AI 行为最硬的一手证据------比回忆"它当时好像说了啥"可靠一万倍。
② 上下文占用怎么算
别只盯 input_tokens。真实窗口占用是三项和:
input_tokens + cache_read_input_tokens + cache_creation_input_tokens
prompt caching 会把绝大部分历史塞进 cache_read,只看 input 会严重低估占用。
③ confabulation:模型失败模式里最迷惑的一种
它不报错、不卡顿、不空白,而是流畅、自信、看起来合理地 生成没有依据的内容,甚至能把自己捏造的前提反过来归到你头上。它是模型机理决定的固有现象,能靠流程压制(重锚、落盘、监控上下文),但没有"修复"开关。所以遇到 AI 答非所问,第一反应不该是"这工具有 bug",而是"我的上下文是不是出问题了"。
写在最后:这次排查最大的收获不是"修好了一个 bug",而是建立了一个习惯------AI 表现异常时,先去翻它的日志,让证据说话。猜测谁都会,但一条 token 曲线能把"到底是工具的锅还是上下文的锅"焊得死死的。