1. 为什么长会话里 Claude Code 会“变笨”:上下文窗口的真实构成
如果你用 Claude Code 写过稍大一点的项目,大概率遇到过这种场景:前几轮它还能准确按你的规范改代码,聊到二三十轮之后,它开始忘记你定的命名规则,甚至把之前明确否掉的方案又提一遍。很多人第一反应是“模型不行了”,但真正的原因往往在上下文窗口的消耗结构上。
Claude Code 的上下文窗口并不只是“你说了什么、它回了什么”。对话历史只是其中一部分,完整来看至少有七类内容在同时占用 token:系统指令(框架规则加工具定义)、CLAUDE.md 持久化规则、对话历史、被读取或编辑过的文件内容、终端命令输出、按需加载的 Skills 指令文本,以及跨会话的自动记忆。这七类里,对话历史和文件内容在典型十轮对话中就能占到七成以上,命令输出紧随其后。也就是说,token 消耗的大头永远是“说了什么和读了什么”,而不是那些看起来很重要的规则文件。
这就引出一个关键认知:上下文管理不是等它满了再清理,而是从你写下第一行 CLAUDE.md 的时候就已经开始了。压缩机制会按优先级丢弃内容——命令输出和文件读取结果最先被移除,因为它们占用大、时效性低、需要时重新执行即可;对话历史则被摘要压缩,具体措辞变成语义概要,精确数值变成“已配置”这样的占位描述。丢失的不是随机信息,而是细节:跨轮强调的规则可能变成模糊总结,讨论过的架构决策原因会消失,具体参数值会被浓缩掉。
所以真正稳定的做法,是把“不能被压缩掉的东西”提前放到不会被压缩的位置。CLAUDE.md 每次压缩后都会重新注入,Skills 在压缩后保留前 5000 token,自动记忆则存在本地目录里跨会话加载。理解这套加载与压缩链路,才能让 Claude Code 在长会话里保持稳定输出。下面我会从 CLAUDE.md 的分层配置讲起,一路走到 Hooks 触发脚本和日志验证,给出可以直接复制的配置。
2. 前置准备:用 TaoToken 接入 Claude Code 并确认模型可用
在折腾上下文配置之前,得先保证 Claude Code 能正常跑起来。Claude Code 本身是一个命令行编码代理,它需要一个兼容 Anthropic 接口的服务端点来驱动模型。我这边用的是 TaoToken 提供的接入方式,它的 API 地址是https://taotoken.net/api,兼容 Anthropic 的 Messages 接口格式,Claude Code 可以直接对接。
先拿到 API Key。打开https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=claude_code_context,在控制台里创建一个新的 Key,复制出来保存好。这个 Key 就是后面所有配置里的ANTHROPIC_AUTH_TOKEN。
接下来配置 Claude Code 的环境变量。Claude Code 读取的是ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN这两个变量。如果你用的是 macOS 或 Linux,可以在~/.zshrc或~/.bashrc里加上:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="sk-你的Key"Windows 的话在 PowerShell 里用$env:ANTHROPIC_BASE_URL="https://taotoken.net/api"临时设置,或者写进系统环境变量。设置完记得source ~/.zshrc让配置生效。
模型 ID 这块要注意,Claude Code 默认会请求claude-sonnet-4-5-20250929这类模型标识。TaoToken 的模型列表可以在https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=claude_code_context查到,选一个支持 Anthropic 接口的模型填进去。如果你在 Claude Code 的配置文件里需要显式指定模型,可以在~/.claude/settings.json里写:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5-20250929" } }这里的三件套——Base URL、Key、Model ID——缺一不可。Base URL 指向 TaoToken 的 API 入口,Key 负责鉴权,Model ID 决定实际调用哪个模型。配好之后在终端里跑claude命令,如果能看到交互界面并且能正常对话,说明接入成功。
如果你更习惯用图形化工具管理多个模型端点,可以看看 Coding Plan 的配置方式,它支持在多个项目间切换不同的模型配置:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=claude_code_context。不过对于上下文调优这个场景,命令行直接配环境变量是最直接的。
3. 可复制配置:CLAUDE.md 分层结构与 Hooks 触发脚本
Claude Code 启动时会从四个位置按顺序查找 CLAUDE.md:托管策略层(组织级)、用户级~/.claude/CLAUDE.md、项目级./CLAUDE.md或./.claude/CLAUDE.md、本地级./CLAUDE.local.md。关键点是这四个文件不是覆盖关系,而是从根目录到当前工作目录全部串联注入。这意味着如果用户级写了“用四空格缩进”,项目级写了“用两空格缩进”,两条规则会同时出现在上下文里,Claude 自行判断优先级,结果可能符合预期也可能不符合。很多“规则不生效”的投诉,根源就在这里。
所以分层配置的核心原则是:用户级放跨项目的通用偏好,项目级放这个项目必须遵守的硬约束,本地级放只在本机生效的临时调整。每个文件建议控制在 200 行以内,超出的部分执行力度会明显下降。
下面是一个经过实战检验的项目级 CLAUDE.md 模板,你可以直接复制到./CLAUDE.md:
# 项目速览 - 项目名: ragent - 框架: Spring Boot 3.5.7 / Java 17 - 向量DB: Milvus 2.6.6 + pgvector - 消息队列: RocketMQ # 编码约定 - 代码风格: Google Java Format (Spotless) - 命名规范: 大驼峰类名 / 小驼峰方法+变量 - 方法长度: < 30 行 - 必须用中文注释公共方法 # 不可违反的架构边界 - 所有 AI 调用走 infra-ai 包,严禁直接 import OpenAI SDK - 数据库访问走 MyBatis-Plus Mapper,禁止写裸 SQL - MCP 相关代码集中在 rag/core/mcp/ 包 # 压缩时必须保留的配置信息 - DB: postgresql://127.0.0.1:5432/ragent, pool=10 - Redis: 127.0.0.1:6379 - RocketMQ nameserver: 127.0.0.1:9876 - Milvus: http://localhost:19530注意最后一段“压缩时必须保留的配置信息”。这段内容会在每次压缩后重新注入,相当于给 Claude 一个不会被摘要掉的提醒便签。把数据库连接串、连接池大小、消息队列地址这类精确值写在这里,比写在对话里可靠得多。
接下来是 Hooks。CLAUDE.md 是建议式管理,Claude 读了会参考但不保证每次都遵守;Hooks 是物理拦截,通过退出码决定操作能不能继续。配置位置和 CLAUDE.md 层级对齐:用户级~/.claude/settings.json,项目级.claude/settings.json,本地级.claude/settings.local.json。
一个实用的 PreToolUse Hook 配置,用来在每次git commit前强制跑 lint:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash .claude/hooks/pre-commit-lint.sh", "async": false } ] } ] } }对应的.claude/hooks/pre-commit-lint.sh脚本:
#!/bin/bash # 从 stdin 读取 Hook 传入的 JSON INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') # 只拦截 git commit 命令 if [[ "$COMMAND" != *"git commit"* ]]; then exit 0 fi # 跑 lint,失败则阻断 if ! mvn spotless:check -q; then echo "Lint 未通过,提交被阻断" >&2 exit 2 fi exit 0退出码 0 表示放行,退出码 2 表示阻断并把 stderr 反馈给 Claude。这个脚本会在每次 Claude 尝试执行git commit时触发,lint 不过就提交不了。
如果你想让 Hook 在后台异步执行、不阻塞主流程,可以加"async": true和"asyncRewake": true。异步 Hook 返回退出码 2 时,会向 Claude 发送系统提醒,通知它异步校验未通过。适合耗时较长的远程代码扫描或依赖漏洞检查。
4. 验证请求:通过日志观察上下文占用变化
配置写好了,怎么确认它真的生效了?Claude Code 会在本地留下会话日志,路径通常在~/.claude/projects/下面,按项目目录哈希分文件夹。每次会话的完整上下文、工具调用记录、压缩事件都会写进去。
最直接的验证方式是观察压缩前后的 token 变化。在长会话里手动触发一次压缩:
/compact 我只关心数据库 schema 的变更历史,其他的都可以压缩然后在日志里搜索compact关键字,能看到压缩前后的 token 统计。我实测下来,一个十轮左右的对话,压缩前上下文占用大概在 80k 到 120k token 之间,压缩后能降到 30k 到 50k。降幅主要来自命令输出和文件读取结果的清除。
如果你想更细粒度地看每一类内容的占用,可以在会话中让 Claude 自己报告:
请统计当前上下文中:对话历史、文件内容、命令输出、CLAUDE.md、Skills 各占多少 tokenClaude 会基于它可见的上下文给出估算。虽然不如日志精确,但能帮你快速定位是哪一类内容在膨胀。
另一个验证点是 Hooks 是否真的触发了。在 Hook 脚本里加一行日志:
echo "$(date '+%Y-%m-%d %H:%M:%S') Hook triggered: $COMMAND" >> /tmp/claude-hooks.log然后让 Claude 执行一次git commit,去看/tmp/claude-hooks.log里有没有记录。如果有,说明 Hook 配置生效了;如果没有,检查settings.json的路径和 matcher 是否写对。
Skills 的验证稍微麻烦一点,因为它的触发是模型自主决策的。你可以在 SKILL.md 的 description 里写清楚触发条件,然后在对话里说一句明显匹配的话,观察 Claude 是否加载了对应的 Skill。日志里搜索skill关键字能看到加载记录。如果 Skill 一直不触发,大概率是 description 写得不够精准——1536 字符的窗口里必须同时说清楚“干什么”“什么时候用”“什么时候不用”。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
配置过程中最容易撞上的几类报错,我按实际遇到的频率排一下。
401 Unauthorized。这个基本是 Key 的问题。先确认ANTHROPIC_AUTH_TOKEN的值没有多余空格或换行,然后去https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=claude_code_context检查 Key 是否还有效、额度是否用完。如果 Key 没问题,检查ANTHROPIC_BASE_URL是不是写成了https://taotoken.net/api,末尾不要带斜杠。
local proxy failed。这个报错通常出现在 Claude Code 尝试连接本地代理但失败的时候。检查你的环境变量里有没有残留的HTTP_PROXY或HTTPS_PROXY设置,如果有就清掉。Claude Code 直连 TaoToken 的 API 端点即可,不需要额外的本地代理层。
reading choices 相关报错。这类错误一般出现在响应格式不符合预期的时候,比如模型返回的 JSON 结构里没有choices字段。先确认你选的 Model ID 是支持 Anthropic Messages 接口格式的,去https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=claude_code_context核对一下。如果模型选对了还报这个错,检查settings.json里有没有重复的ANTHROPIC_MODEL定义,多个定义冲突时 Claude Code 可能取到了不支持的值。
OAuth 相关报错。Claude Code 某些版本会尝试走 OAuth 流程做设备授权。如果你用的是 API Key 方式接入,需要在配置里明确禁用 OAuth。在~/.claude/settings.json里加上:
{ "forceApiKeyAuth": true }这样 Claude Code 就不会再尝试 OAuth 授权,直接用你配的 Key 走 API 调用。
还有一个隐蔽的坑:CLAUDE.md 层级冲突导致规则不生效。排查方法是把所有层级的 CLAUDE.md 找出来,搜索冲突的关键词:
find . -name "CLAUDE.md" -o -name "CLAUDE.local.md" | xargs grep "缩进"如果用户级和项目级都定义了缩进规则且不一致,删掉其中一个,或者把项目级的规则写得更具体来覆盖。
6. 把上下文管理变成日常习惯:从配置到验证的闭环
聊到这里,配置和排障的链路基本走完了。最后说几个我在实际项目里养成的习惯,能让这套机制真正跑起来。
第一,每次开新项目先写 CLAUDE.md,不要等到规则不生效了才补。项目级文件控制在 100 行以内,只放硬约束和压缩必须保留的配置值。用户级文件放跨项目的通用偏好,比如代码风格和命名规范。本地级文件放临时调整,记得加进.gitignore。
第二,Hooks 只用来管“必须执行”的规则。lint 检查、提交签名、敏感命令审计这类不能妥协的事情交给 Hooks,其他建议性的规范留在 CLAUDE.md 里。Hook 脚本尽量保持简单,一个脚本只做一件事,方便排查。
第三,长会话里主动触发/compact并指定焦点。不要等自动压缩,自动压缩的保留策略是 Claude 自己判断的,你介入指定“只保留数据库 schema 变更历史”能让压缩结果更符合当前任务需要。
第四,定期检查~/.claude/projects/下的日志,看看哪类内容占用增长最快。如果发现命令输出占比异常高,说明你在会话里跑了太多一次性命令,考虑把这类操作放到单独的终端窗口里做,不要经过 Claude Code。
这套配置跑顺之后,Claude Code 在长会话里的稳定性会有明显提升。规则不会因为压缩而丢失,必须执行的操作有物理拦截,上下文占用有日志可查。把时间花在理解工具的运作方式上,比花在猜测工具的脾气上,回报大得多。