1. 为什么你的 Claude Code 每次开新会话都像失忆
用 Claude Code 写代码最让人抓狂的场景,不是它写不出来,而是它每次都像第一次进这个项目。你昨天刚跟它强调过「这个仓库统一用 pnpm,别碰 npm」,今天新开一个会话,它又顺手给你跑了个npm install,锁文件直接冲突。你上周花半小时跟它解释清楚本地测试必须先起 Redis 容器,这周它看到connection refused又开始怀疑人生,让你去改连接配置。
这就是没有持久上下文时 AI 编程的真实体验:上下文是「一次性」的。项目越大、团队规约越多,这种重复沟通的成本就越高。更麻烦的是幻觉——当它拿不到团队沉淀的架构约定时,就会按自己的「常识」生成代码,缩进风格、错误处理格式、目录结构全凭它心情,你还得手动改回来。
Claude Code 给出的解法是两套互补的记忆系统。一套是你手动写的「说明书」CLAUDE.md,每次会话启动时加载,负责告诉它这个项目是什么、规矩是什么;另一套是它自己维护的「笔记本」MEMORY.md 及其背后的自动记忆目录,负责在协作过程中把踩过的坑、你的偏好悄悄记下来,跨会话延续。
这篇文章聚焦这两套机制的职责分工、加载顺序,以及最关键的——怎么配置、怎么验证一条新记忆真的被读进去了。适合那些希望让项目上下文跨会话延续、不想每次重复投喂 prompt 的开发者。下面从目录结构讲到可复制的字段模板,再到实测验证方法,一步步来。
2. CLAUDE.md 与 MEMORY.md 的职责分工和加载顺序
先把两套系统的定位说清楚,不然后面配置容易混。
CLAUDE.md 是你写给 AI 的项目说明书,本质是一个 Markdown 文件,放在项目根目录。它每次会话启动时被加载,提供持久的全局上下文。你可以把它理解成「专门给 AI 看的 README」。它适合放那些稳定、明确、团队共识级别的内容:构建命令、测试方法、命名规约、Git 工作流、目录约定。
MEMORY.md 是AI 写给自己的笔记索引,位于用户本地的~/.claude/projects/<项目名>/memory/目录下。它由自动记忆机制维护,每次启动只加载前 200 行作为索引,详细内容拆分到同目录下的主题文件里按需读取。它适合放那些在协作中动态积累的经验:某个报错的原因、你纠正过的依赖用法、临时但重要的环境前提。
加载顺序上,CLAUDE.md 属于「启动即全量加载」的显式上下文,优先级高、稳定;MEMORY.md 属于「启动加载索引 + 按需拉取详情」的隐式上下文,灵活但受 200 行限制。两者不冲突,是叠加关系。一个典型的会话里,Claude 先读 CLAUDE.md 建立项目认知,再读 MEMORY.md 索引补充历史经验,遇到具体文件时再按.claude/rules/里的路径规则触发对应规则。
这里有个容易踩的坑:很多人把所有东西都塞进 CLAUDE.md,写到四五百行,结果 AI 注意力被稀释,关键规则反而被忽略。官方建议单个指令文件控制在 200 行以内。超出的部分应该拆到.claude/rules/目录,用 YAML 的paths字段做精准路由,只在真正读写对应文件时才加载。
举个路由规则的例子,放在.claude/rules/api.md:
--- paths: - "src/api/**/*.ts" --- # API 开发规则 - 所有 API 接口必须包含输入验证 - 使用标准的错误响应格式 - 必须包含 OpenAPI 的文档注释这段规则只有当 Claude 真正去读写src/api/下的 TypeScript 文件时才会触发加载,平时不占用上下文空间。这个设计对 monorepo 巨石应用特别友好。
指令写法上,具体程度直接决定依从性。对比一下:
| 模糊写法(容易翻车) | 具体写法(推荐) |
|---|---|
| 把代码格式化好 | 使用 2 个空格缩进 |
| 测试你的修改 | 在 commit 之前必须运行npm test |
| 注意错误处理 | 所有 async 函数用 try/catch 包裹并记录日志 |
指令越具体,执行越稳定。这是配置 CLAUDE.md 时最值得记住的一条。
3. 可复制的记忆文件目录结构与字段模板
这一节给可直接抄的配置。先看整体目录结构,再逐个文件给模板。
项目侧的显式记忆结构:
your-project/ ├── CLAUDE.md # 项目说明书,启动全量加载 ├── .claude/ │ └── rules/ │ ├── api.md # 按 paths 路由的规则 │ ├── frontend.md │ └── git-workflow.md └── docs/ └── git-instructions.md # 被 @ 引用的外部文档用户侧的自动记忆结构(由 Claude Code 自动生成,也可手动编辑):
~/.claude/projects/<项目名>/memory/ ├── MEMORY.md # 索引文件,启动仅加载前 200 行 ├── debugging.md # 主题文件,按需读取 ├── api-conventions.md └── environment.md根目录的 CLAUDE.md 模板,控制在 200 行内:
# 项目概览 这是一个 React 18 + Vite 的前端项目,包管理器统一使用 pnpm。 请查看 @README 获取项目概览,查阅 @package.json 了解可用的构建命令。 # 附加规则 - Git 工作流 @docs/git-instructions.md # 核心约定 - 包管理器:统一使用 `pnpm`,禁止使用 `npm`,避免锁文件冲突 - 组件写法:页面组件必须使用函数式写法,配合 Hooks - 样式方案:Tailwind CSS,禁止引入额外的 CSS-in-JS 库 - 缩进:2 个空格 # 常用命令 - 开发:`pnpm dev` - 单元测试:`pnpm run test:watch` - 格式化:`pnpm run lint --fix` - 构建:`pnpm build`注意@语法,它用来引入外部文件,避免主文件膨胀。@README、@package.json、@docs/git-instructions.md都会被解析并加载。
自动记忆的索引文件 MEMORY.md 大致长这样,你可以手动编辑它:
# 项目记忆索引 (Project Memory) ## 核心偏好与架构 - 包管理器:团队统一使用 `pnpm`,绝对不要使用 `npm` 导致锁文件冲突。 - 框架约定:使用 React 18 + Tailwind CSS,页面组件必须使用函数式写法。 - API 规范:详细的接口错误码处理和格式约定,请在需要时查阅 `api-conventions.md`。 ## 历史排错经验 (Debugging) - Redis 报错:本地测试时若提示 "connection refused",是因为没起容器, 必须先执行 `docker-compose up redis -d`。 - CORS 跨域:本地开发已在 `vite.config.ts` 配好了 proxy, 调试时不需要再让后端改跨域头配置。详细排错历史见 `debugging.md`。 ## 常用高频命令 - 单元测试:`pnpm run test:watch` - 格式化:`pnpm run lint --fix`主题文件debugging.md放长内容,按需读取:
# 排错历史 ## Redis connection refused 现象:本地跑集成测试时报 `ECONNREFUSED 127.0.0.1:6379`。 原因:本地 Redis 容器未启动。 解决:`docker-compose up redis -d`,等待健康检查通过后再跑测试。 ## Vite proxy 与后端 CORS 现象:前端请求后端接口出现跨域。 原因:误以为需要后端加 CORS 头。 解决:本地已在 `vite.config.ts` 配置 proxy,检查 proxy 路径是否匹配即可。如果你用的是 Claude Code 的配置文件方式接入第三方模型服务,settings.json里可以这样写(路径与字段名保持一致):
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "你的_API_Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }三件套要写全:Base URL、Key、Model ID,缺一个都可能连不上。Key 在控制台的 API Keys 页面生成,接入细节可以对照官方接入文档。
4. 验证新增记忆条目被正确读取与生效
配置写完不算完,得验证它真的被读进去了。这一步很多人跳过,结果以为配好了,实际根本没加载。
先确认版本。自动记忆特性要求 Claude Code v2.1.59 及以上。在终端跑:
claude --version低于这个版本先升级。
验证 CLAUDE.md 是否加载,最直接的方式是在会话里问它:
请复述一下本项目的包管理器约定和常用测试命令。如果它答出「统一使用 pnpm,禁止 npm」和pnpm run test:watch,说明 CLAUDE.md 被正确读取。如果答得含糊或者答错,检查文件是否在项目根目录、命名是否为CLAUDE.md(大小写敏感)。
验证自动记忆,先手动往 MEMORY.md 加一条测试条目,比如在「历史排错经验」下加:
- 测试记忆:本项目所有日期格式化统一使用 dayjs,禁止引入 moment。保存后新开一个会话,问它:
本项目日期格式化用什么库?如果它回答 dayjs 并提到禁止 moment,说明 MEMORY.md 索引被加载了。注意只加载前 200 行,所以测试条目别放到 200 行之后。
再验证主题文件的按需读取。在debugging.md里加一条独特的内容,然后在会话里触发相关场景,比如问:
本地集成测试报 Redis connection refused 怎么处理?它应该能引用debugging.md里的解决步骤。如果只答了个大概,说明主题文件没被拉取,检查 MEMORY.md 里是否有指向该文件的引用。
验证.claude/rules/的路径路由,可以故意在src/api/下创建一个文件并让 Claude 修改它,观察它是否应用了 api.md 里的规则。也可以在会话里直接问「修改 src/api 下的文件时有哪些规则」,看它能否说出输入验证、标准错误响应格式这些条目。
实测下来,最可靠的验证信号是终端里出现的Writing memory和Recalled memory提示。看到Recalled memory就说明它正在翻阅记忆,看到Writing memory说明它正在更新笔记。这两个提示是判断自动记忆是否工作的直接证据。
5. 常见报错排查:401、local proxy failed 与记忆不生效
配置过程中会遇到几类典型报错,逐个拆。
401 Unauthorized。多半是 Key 没配对或者 Base URL 写错。检查settings.json里的ANTHROPIC_AUTH_TOKEN是否完整复制,有没有多余空格。Base URL 应该是https://taotoken.net/api,不要多加路径后缀。如果用的是环境变量方式,确认变量名拼写正确,ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN一个都不能少。
local proxy failed。这个报错通常出现在网络层,说明请求没能到达目标地址。先确认 Base URL 可达,再检查本地是否有其他进程占用了端口。如果是公司网络环境,确认出口策略允许访问对应域名。这类问题跟记忆机制无关,是接入层的问题,先解决连通性再谈记忆。
reading choices 相关报错。这通常出现在响应解析阶段,说明返回体格式不符合预期。常见原因是 Model ID 写错,比如把claude-sonnet-4-5写成了别的名字,导致服务端返回了非预期结构。核对 Model ID 是否与文档一致。
OAuth 相关报错。如果你用的是 OAuth 方式登录,报错往往跟 token 过期有关。重新走一遍登录流程即可。如果同时配置了 OAuth 和 API Key,可能产生冲突,建议二选一。
记忆不生效。这是最容易被误判的一类。排查顺序:第一,确认版本 ≥ v2.1.59;第二,确认 MEMORY.md 在~/.claude/projects/<项目名>/memory/下,项目名要匹配;第三,确认测试条目在前 200 行内;第四,确认 CLAUDE.md 在项目根目录且命名正确;第五,新开会话验证,旧会话不会重新加载。
规则不触发。检查.claude/rules/下文件的 YAML front matter 格式,paths字段的 glob 是否匹配你操作的文件路径。src/api/**/*.ts匹配的是src/api/下任意层级的 ts 文件,如果你的文件在src/apis/下就不匹配。
指令被忽略。回到第 2 节说的,检查指令是否够具体。「格式化好代码」这种写法 AI 无法稳定执行,改成「使用 2 个空格缩进」这种可判定的表述。
排查时建议一次只改一个变量,改完新开会话验证,避免多个改动叠加导致定位困难。
6. 把记忆机制用成长期资产
配置记忆文件这件事,真正的价值不在于省下几次重复输入 prompt 的时间,而在于它把团队的隐性知识显性化了。以前「为什么必须用 pnpm」这种约定只存在于老员工的脑子里,新人(包括 AI)都得踩一遍坑才知道。现在它被写进 CLAUDE.md,成为项目资产的一部分。
我的建议是分两层维护。CLAUDE.md 保持精简,只放稳定共识,控制在 200 行内,超出的拆到.claude/rules/。MEMORY.md 让它自然生长,但定期用/memory命令调出来审一遍,把过时的条目删掉,把高频踩坑点提到索引前部。自动记忆会记错,纯文本的好处就是你能直接改。
如果你还在用第三方模型服务跑 Claude Code,接入配置记得三件套写全,Base URL 用https://taotoken.net/api,Key 在控制台生成,Model ID 核对清楚。接入文档里有完整的字段说明,遇到 401 或 proxy 报错先回第 5 节对照排查。
长期做编码和 Agent 任务的,可以考虑 Coding Plan,把记忆机制和稳定的模型服务结合起来,跨会话的上下文延续才真正跑得通。想先验证模型效果的,直接去模型对话页面试几轮,感受一下记忆加载前后的差异。