Claude Code 不理解整个项目怎么办

Claude Code 反复改同一个文件、或完全忽略别的 workspace?在仓库根目录跑 /init,用 CLAUDE.md 锚定上下文,并从根目录启动会话。2026 年 6 月核实。

你让 Claude Code 在一个中等规模仓库里做改动,结果它反复修改 src/index.ts 一个文件,完全忽略你提到的 apps/web/packages/api/;在 monorepo 里它根本看不出 workspace 边界;要不就是把测试写在了源码同目录而不是 __tests__/。每次都是同一个根本原因:Claude Code 缺少项目地图。

最快的修法: 在仓库根目录跑 /init。Claude Code 会用一个子代理扫描代码库,写出一份起步用的 CLAUDE.md,里面有你的构建命令、测试命令和关键目录。审一遍,补上它推断不出来的两三条规则(workspace 依赖图、“别碰”的路径),然后重启会话。光这一步就能解决大多数情况。本文剩下的部分讲 /init 不够用时该怎么办,以及如何确认问题真的修好了。

为什么会这样

Claude Code 本质是一个带文件工具的 LLM 会话。每次会话都从一个全新的上下文窗口开始,它对你项目的全部认知只来自三件事:启动时所在的工作目录、启动时加载的那些 CLAUDE.md 文件、以及你在 prompt 里提到的路径。三者任一缺失或失真,它就会陷入”以管窥豹”的模式——只能用 ls 一层一层地探结构,在任何非平凡的仓库里都容易迷路。

官方 memory 文档,Claude Code 会从工作目录向上遍历目录树,加载沿途每一个 CLAUDE.mdCLAUDE.local.md,再加上 ~/.claude/CLAUDE.md(用户级)以及托管策略文件。目录里的文件在启动时不会加载——只有当 Claude 读到那个子目录里的文件时才按需加载。很多”它忽略了某个包”的报告,根源就在这个不对称上。

你属于哪一类?

症状可能原因先检查跳到
每开一个新会话就忘掉约定没有 CLAUDE.mdls CLAUDE.md 没结果原因 1 / Step 1
看不到同级的包或共享类型在子目录里启动的/status!pwd 不是仓库根原因 2 / Step 2
把框架代码当成”你的”组件返回node_modules / dist 干扰搜索结果里有 node_modules/...原因 3 / Step 3
不知道 workspace 依赖拓扑没把 workspace 地图写下来让它”列出所有包”时只列出当前那个原因 4 / Step 1
改它上次碰过的文件,而不是该改的prompt 太模糊你的 prompt 里没有文件路径原因 5 / Step 5
长对话里忘掉早期定下的规则上下文被低价值文本塞满会话超过 30 轮、/context 显示占用很高原因 6 / Step 6

常见原因

按命中率从高到低排列。

1. 没有 CLAUDE.md——每次会话都从零开始

最高频的原因。Claude Code 启动时会自动加载目录层级里的 CLAUDE.md 文件,把内容注入上下文。没有它,Claude 只能一个文件一个文件地探结构,遇到任何非平凡的 monorepo 基本就迷路。

如何判断: 在项目根跑 ls CLAUDE.md。不存在就是这种情况。你也可以在会话里跑 /memory——它会列出当前加载的所有 CLAUDE.mdCLAUDE.local.md 和规则文件。如果列表是空的,说明 Claude 完全是盲飞。

2. 工作目录起点不对

apps/web/ 启动 Claude Code,它就把这一层当成整个项目——它看不到 packages/shared/ 里的共享类型,因为那是同级目录,不是祖先目录。反过来从 ~/code/ 这种父目录启动,它又会被几十个无关项目淹没。

如何判断: 会话一开始就跑 /status(或 !pwd),对照真实的项目根——通常是含有 .git/ 或 workspace 清单文件(pnpm-workspace.yaml、带 workspaces 字段的根 package.jsonnx.json)的那一层。

3. node_modules / dist / .next 等目录淹没真实代码

Claude Code 默认会跳过一组路径,但它的 Grep 和 Glob 工具仍可能扫到 node_modules 里的 vendored 代码。你想要”那个 Button 组件”,它却返回框架自带的那个,而不是你写的。

如何判断: 让 Claude Code 列出”仓库里所有 Button 组件”。如果结果里含有 node_modules/...,就是这个原因。

4. monorepo 没有 workspace 地图

pnpm workspace、Turborepo、Nx——包之间靠 workspace:* 互相引用。Claude Code 不会主动去解析 pnpm-workspace.yamlnx.json 来推导依赖图。你得自己把它写下来。

如何判断: 让它”列出本仓库的每个包,以及谁依赖谁”。如果它只知道当前这个包,就是这种情况。

5. prompt 里的指代太模糊

“修一下登录的 bug”——它不知道登录代码在哪。“那个组件不对”——哪个?模糊的 prompt 会让 Claude Code 默认去改它上次碰过的那个文件,而那往往是错的。

如何判断: 回看你上一轮 prompt。如果里面没有具体的文件路径或函数名,就是这个原因。

6. 上下文窗口被低价值内容塞满

如果会话早期你贴进去过大量日志、build 输出或长 stack trace,真正相关的代码就会被挤到活跃窗口的边缘,早先定下的约定也开始失效。

如何判断:/context——它会把吃掉窗口的内容可视化出来。如果旧日志或会话记录占了大头、且会话已经很长(30+ 轮),就是这个原因。

最短修复路径

Step 1:写(或生成)一份 CLAUDE.md 项目地图

先跑 /init——它会从你的真实代码里写出一份草稿:它会派一个子代理去读 source-of-truth 文件(package.jsonrequirements.txtMakefileREADME)并梳理目录树。如果已经存在 CLAUDE.md/init 会建议改进而不是覆盖。想要一个还能顺带搭建 skills 和 hooks 的引导式流程,可以在运行前设置 CLAUDE_CODE_NEW_INIT=1——这是一个交互式多阶段流程,会问追问、并在写入任何文件前给你一份可审阅的方案。然后手动编辑,把 workspace 依赖图和”别碰”规则写明确。项目级 CLAUDE.md 放在 ./CLAUDE.md./.claude/CLAUDE.md 都行,两个位置都会被加载。控制在 200 行以内——Anthropic 文档指出文件越长越占上下文、Claude 遵守度反而下降。

# Project Map

This is a pnpm monorepo with 3 workspaces:
- `apps/web/`  Next.js frontend (port 3000)
- `apps/api/`  Hono backend (port 8787, Cloudflare Worker)
- `packages/shared/`  Zod schemas + types used by both
  - `apps/web` and `apps/api` both import from `packages/shared`; shared never imports back

## Conventions

- TypeScript strict mode everywhere
- Tests live in `__tests__/` next to source, Vitest
- API routes follow REST: `GET /resource`, `POST /resource`, etc.
- Never edit files under `packages/shared/dist/` — build output

## Commands

- `pnpm dev` — runs web + api concurrently
- `pnpm test` — runs all workspaces
- `pnpm typecheck` — must pass before commit

## Don't touch

- `infra/terraform/` — owned by ops
- `apps/web/public/legacy/` — frozen

写得越具体、越可验证越好。“API handlers live in src/api/handlers/” 远胜过”keep files organized”。想让地图保持 DRY,可以用 @path 语法导入其他文件——例如 See @README.md for overview——它会在启动时展开(相对路径相对于发出 import 的那个文件解析,最多四层)。导入有助于组织结构,但并不省上下文,因为被导入的文件同样在启动时一并加载进来。

Step 2:从正确的工作目录启动

找到项目根(含有 .git/ 或 workspace 清单文件的那一层),从那里启动:

cd ~/code/my-monorepo
claude

不要从 ~/code/ 启动后再 cd 进去——Claude Code 启动时的 CLAUDE.md 扫描已经做完了,之后它不会再补读祖先目录里的地图。如果你确实需要把一个同级目录纳入范围(比如一个共享配置仓库),就显式加进去:claude --add-dir ../shared-config。想同时加载那个目录里的 CLAUDE.md,设置 CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1

Step 3:砍掉噪声——声明忽略项、拒绝密钥

截至 2026 年 6 月,并不存在官方的 .claudeignore 文件(这是个尚未实现的功能请求,anthropics/claude-code #29455)。真正能用的有两种机制:

  1. 在 CLAUDE.md 里告诉它哪些生成目录可以跳过,省得模型在上面浪费回合:

    ## Ignore these (generated / vendored)
    - `node_modules/`, `dist/`, `.next/`, `.turbo/`, `coverage/`
    - `**/*.generated.ts`
    - `apps/web/public/legacy/`
  2. 硬性封锁敏感文件,在 .claude/settings.json 里用 permissions.deny——和 CLAUDE.md 里的提示不同,这条会把匹配到的文件从发现、搜索和读取里都排除掉:

    \{
      "permissions": \{
        "deny": ["Read(./.env)", "Read(./.env.*)", "Read(./**/secrets/**)"]
      \}
    \}

    但把 permissions.deny 当成护栏,别当成保险箱。截至 2026 年 6 月,仍有未关闭的报告称 deny 规则可能被绕过——比如密钥通过 symlink 或某个不寻常的路径被读到(anthropics/claude-code 的 issue #6699 和 #24846)。请保持 Claude Code 为最新版本(claude --version),如果某个文件绝对不能被读到,就用一个 PreToolUse hook 来强制——官方文档正是推荐用它来”无论 Claude 怎么决定都拦下这个动作”。hook 作为 shell 命令运行,可以直接让那次 Read 失败,而光靠配置一直做不到这点可靠。

在大型 monorepo 里,如果其他团队的 CLAUDE.md 被一起拉进来污染上下文,可以在 .claude/settings.local.json 里用 claudeMdExcludes 按 glob 跳过它们(匹配的是绝对路径;数组会在 user、project、local、托管策略各设置层之间合并)。

Step 4:第一轮 prompt 用”先读这些再确认”

别一上来就直接派活。先发:

First read CLAUDE.md, then ls the structure of apps/ and packages/
and confirm you understand the workspace layout. Then we'll begin.

让它先建立心智模型,比你派完活之后再让它来回试错快得多。对 monorepo 来说,这一步还会强制加载各子目录里的嵌套 CLAUDE.md——别忘了,那些文件只有当 Claude 读到对应子树里的文件时才会加载。

Step 5:在 prompt 里用具体路径

不要说”修登录的 bug”,要说”修 apps/web/src/pages/login.tsx 第 42 行 handleSubmit 里的错误处理”。具体路径让 Claude Code 直接跳过猜测和 grep 的环节。

Step 6:长会话用 /compact 或 /clear

Claude Code 会在窗口接近塞满时自动 compact,但等到那一刻,它早就在被压缩的上下文里干活了(具体触发点在不同版本间一直在变,截至 2026 年 6 月有人报告在百分之八十出头、也有人报告在九十几,所以别指望它)。跑 /context 看占用;当你超过 30-40 轮、或占用条很高时,跑 /compact 让 Claude Code 就地总结当前状态。你也可以给它聚焦:/compact keep the auth refactor plan and the file list。如果是真正全新的任务,用 /clear 把历史整个清空。两者都有个好消息:项目根的 CLAUDE.md 能挺过 /compact(Claude 会从磁盘重新读取),所以你的项目地图永远不会是丢失的那一块——但子目录里的嵌套地图不会被重新注入,要等 Claude 下次读到那里的文件时才重新加载。

如何确认修好了

  1. 从仓库根重启会话,跑 /memory。你的项目 CLAUDE.md(以及任何嵌套的)应该显示为已加载。如果没有,Claude 就看不到它。
  2. 问:“List every workspace in this repo and what each one depends on.”(列出本仓库每个 workspace 及其依赖。)修好的配置会直接从地图作答、不用 grep;坏掉的只会说出当前这个包。
  3. 让它找”the Button component we own”(我们自己的 Button 组件)。路径应该在 apps/packages/ 下,绝不会是 node_modules/
  4. 派一个会触及非默认目录的小型真实任务。如果它第一次就改对文件、而不是默认去改上次碰过的那个,说明地图起作用了。

常见问题

Claude Code 会读 AGENTS.md 或 .cursorrules 吗?

它直接读的是 CLAUDE.md,不是 AGENTS.md。如果你的仓库已经有 AGENTS.md,就在 CLAUDE.md 顶部放一行导入(@AGENTS.md),或者做个软链(ln -s AGENTS.md CLAUDE.md),让两个工具共用同一份源。Windows 上做 symlink 需要管理员权限或开发者模式,所以更推荐用 @AGENTS.md 导入。在已有 AGENTS.md.cursorrules.devin/rules/.windsurfrules 的仓库里跑 /init,它会读取这些文件,把相关内容折进生成的 CLAUDE.md

CLAUDE.md 已经加载了,但 Claude 还是不守某条规则。为什么?

CLAUDE.md 是在 system prompt 之后作为上下文交付的,不是被强制执行的配置,所以并不保证遵守——尤其对模糊或互相冲突的规则。把指令写得具体、可验证,并检查你的用户级、项目级、嵌套各文件之间有没有矛盾。如果某件事必须在固定时机执行(每次提交前、每次编辑后),改用 hook——它无论 Claude 怎么决定都会照常执行。

应该写一个大的 CLAUDE.md,还是拆开?

对大多数仓库,一个 200 行以内的根文件最好。当它变大时,把按主题或按路径的指引挪到 .claude/rules/*.md。带 paths: frontmatter glob 的规则只在 Claude 触及匹配的文件时才加载,这样能让大型代码库里”始终在线”的上下文保持精简。

自动记忆(auto memory)和 CLAUDE.md 有什么区别?

CLAUDE.md 是你写的;auto memory 是 Claude 跨会话给自己写的(构建命令、调试心得、它发现的偏好),按仓库存放在 ~/.claude/projects/<project>/memory/。其 MEMORY.md 的前 200 行(或前 25KB)会在每次会话时加载。auto memory 需要 Claude Code v2.1.59 或更新版本;用 claude --version 查看。跑 /memory 可以浏览或编辑它保存的内容。

昨天它还懂这个项目,今天就不懂了,发生了什么?

通常是三个嫌疑之一:你从一个不同的目录启动了(于是加载了一组不同的 CLAUDE.md);队友改动或移动了地图;或者昨天的”理解”只存在于对话里,从没进过 CLAUDE.md。任何你想让它持久记住的东西,都必须写进文件,而不是只留在聊天里。

预防

  • CLAUDE.md 当成活文档——新增模块、改了约定就更新它,尤其是当你第二次纠正 Claude 同一个问题时。
  • 每个 workspace 里也放一份 CLAUDE.md;Claude Code 在读那个子树里的文件时会加载最近的那份。
  • 复杂任务用两段式 prompt:“先列计划、确认理解,再执行。”
  • 把长期有效的规则(“测试要先写 in-memory mock”)编码进 CLAUDE.md,而不是每次 prompt 里重复。
  • 长会话主动 /compact,别等上下文塞到窗口边缘才被动触发自动压缩。

相关阅读

标签: #Claude #排查 #排查