多份CLAUDE.md之间用@a.md、@b.md互相引用,Claude Code 加载记忆文件时就会抛Error loading memory files: circular reference detected,或者干脆启动卡死、没有任何提示。这篇文章按排障顺序走:先用 TaoToken(官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= )把 Claude Code 的模型通道配通——注册、创建 Key、把 Base URL 填成https://taotoken.net/api写进settings.json——再用这条通道让 Claude Code 去读项目里的几份CLAUDE.md、列出@引用边、判断是否存在有向环,最后对着排查清单逐条确认闭环是否已经被拆成单向结构。需要先说清楚边界:TaoToken 在这里只负责给 Claude Code 提供 Key 和 Base URL,它不参与CLAUDE.md的引用展开逻辑,也不做循环检测,更不替代grep和手画的引用关系图。很多人把两类问题混在一起排查,才会觉得"改了引用关系还是卡"。
一、报错现场:卡死和 circular reference 往往是同一条根因
先还原现象。项目里存在多份CLAUDE.md,通过@其他文件.md的形式互相补充内容。某次调整引用关系之后,Claude Code 启动明显变慢,严重时完全无响应;把每个文件单独打开看,@语法都写得没错,路径也没有明显笔误。
这类现象的背后是一次递归展开:Claude Code 读到@行之后,需要把被引用文件的内容取出来,再递归处理被引用文件里的@行,最终拼接成完整的记忆内容。如果引用关系形成了环路——a.md引用b.md,b.md引用c.md,c.md又指回a.md——展开逻辑要么陷入无限递归,要么需要处理的展开结果呈指数级膨胀。表现出来就是两种:一种是版本内置了检测,主动报出循环引用;另一种是没触发明确检测,资源消耗持续增长,界面看起来只是"卡住"。
这里有个容易被忽略的前提:原文给出的排查手段(用grep列出所有引用、改成单向层级、注释掉@行做排除法)都默认 Claude Code 本身是能启动、能响应、能返回内容的。如果settings.json里的模型通道没配通,你面对的可能根本不是循环引用,而是请求发不出去导致的启动异常,两者的外部表现高度相似:都卡、都慢、都没有有效日志。所以排障的第一步不是冲进CLAUDE.md里改引用,而是先把通道和报错区分开。
一个粗糙但有效的区分方式:把项目里的CLAUDE.md暂时重命名或移出目录,重新启动 Claude Code。如果依然卡死或反复重试,问题在通道侧,去看settings.json的ANTHROPIC_*配置;如果启动恢复正常,问题就在记忆文件的引用链上,回到第一节描述的递归展开逻辑继续排查。
二、TaoToken 前置:Key 和 Base URL 只解决"通道"这一件事
把通道补上只需要三件事:注册账号、创建 Key、确认 Base URL 的写法。
打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 完成注册后,进入 API Keys 页面创建一把 Key,对应地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys ,创建后先复制留存,后面写进settings.json的就是这一串,本文统一用YOUR_API_KEY代指。
Base URL 固定写成https://taotoken.net/api,三个注意点:结尾不要补/v1,不要拼任何查询参数,也不要带 UTM 后缀。请求路径里的/v1/messages是接口自身的部分,和 Base URL 是两码事,两处叠加就会变成/api/v1/v1/messages。完整的接入写法在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc 里有说明,配置前对照一遍比事后猜错要省事。
要强调的是职责范围。TaoToken 提供的是模型调用的入口凭据和地址,它不会读取你的CLAUDE.md,不会做引用展开,也不会告诉你哪两个文件构成了环。判断环路这件事,仍然要靠grep列边、靠关系图或者一段判环脚本。把这条边界记住,排查时就不会在错误的层面反复试错。
三、可复制配置:写进 Claude Code 的 settings.json
Claude Code 的配置可以直接落在用户级~/.claude/settings.json,也可以在项目里建.claude/settings.json做项目级覆盖。用户级适用于本机所有项目,项目级适合团队各自拉取后开箱可用。把下面这段填进去:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "MODEL_ID" } }几个细节决定了这段配置能不能一次跑通。ANTHROPIC_BASE_URL的值必须是裸地址,不要写成https://taotoken.net/api/,也不要写成https://taotoken.net/api/v1。ANTHROPIC_AUTH_TOKEN的位置放创建出来的 Key,注意它是字符串,要带引号。ANTHROPIC_MODEL填实际要用的模型标识,这个值在模型对话页面确认,不要凭记忆写。部分版本对键名更敏感,同时存在ANTHROPIC_API_KEY和ANTHROPIC_AUTH_TOKEN两种写法,选当前版本认可的那一个,不要两个键填不同的值,否则行为会变得难以预测。
还有一个高频坑:shell 里如果之前export过ANTHROPIC_BASE_URL或ANTHROPIC_AUTH_TOKEN,它会盖掉settings.json里的同名项。排查时先执行env | grep ANTHROPIC看一下当前 shell 的取值,确认没有残留的旧地址。改完配置文件后要重新开一个 Claude Code 会话,正在跑的进程不会自动重载。
四、验证请求:先打通一次调用,再让 Claude Code 画引用链
配置写完不要直接去啃CLAUDE.md,先做一次最小验证,确认通道本身是可用的。
curl -sS https://taotoken.net/api/v1/messages \ -H "x-api-key: YOUR_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "MODEL_ID", "max_tokens": 64, "messages": [ { "role": "user", "content": "只回复 ok" } ] }'成功的返回是一个 JSON 对象,包含content数组和其中的文本字段,stop_reason是一个正常结束值,HTTP 状态码是 200。如果看到 401、403,问题在 Key;看到 404 或者路径相关的错误,回头检查 Base URL 是否被写成了带/v1或带参数的形式;如果长时间无响应,先确认网络出口是否允许访问该地址,再确认本地有没有代理把它拦下来。
通道确认之后,进入真正的排查环节。在 Claude Code 里发一条明确的分析指令,让它在不修改任何文件的前提下做只读梳理:
扫描仓库内所有名为 CLAUDE.md 的文件,找出每一行以 @ 开头且指向 .md 文件的引用, 输出 "引用方文件 -> 被引用文件" 的边列表,不要修改任何文件, 然后基于这些边判断是否存在有向环,如果存在,把闭环节点按顺序列出来。拿到边列表之后,自己再核对一遍。命令行上可以直接用grep做交叉验证:
grep -rn --include="CLAUDE.md" -E "^\s*@" .这一步的作用是把"引用方"和"被引用方"两列对齐。注意把子目录一起扫进来,只在仓库根目录看一份CLAUDE.md很容易漏掉藏在packages/或services/下的引用源。
要判断有没有环,手工画图在小项目上够用,文件一多就建议上一个判环脚本,思路是把刚才的边表读进来做一次深度优先遍历:
import re import pathlib import collections root = pathlib.Path(".") graph = collections.defaultdict(set) pattern = re.compile(r"^\s*@([^\s]+\.md)\s*$") for f in root.rglob("CLAUDE.md"): rel = str(f.relative_to(root)) for line in f.read_text(encoding="utf-8").splitlines(): m = pattern.match(line) if m: graph[rel].add(m.group(1).strip()) WHITE, GRAY, BLACK = 0, 1, 2 color = collections.defaultdict(int) stack = [] def dfs(node): color[node] = GRAY stack.append(node) for nxt in graph.get(node, ()): if color[nxt] == GRAY: i = stack.index(nxt) print("闭环:", " -> ".join(stack[i:] + [nxt])) return True if color[nxt] == WHITE and dfs(nxt): return True stack.pop() color[node] = BLACK return False for node in list(graph): if color[node] == WHITE and dfs(node): break脚本只做只读分析,不会改动CLAUDE.md。输出里出现闭环链条,就说明第一节描述的递归展开问题确实存在。如果脚本报不出环,但启动依然缓慢,那属于下一节的第二类问题——引用链过深,不是真闭环。
五、本篇常见错排查:通道侧和记忆文件侧分开看
先把通道侧的错列出来,它们制造的现象和循环引用很像,混在一起排查最容易走弯路。
第一,Base URL 写成https://taotoken.net/api/v1。这是最高频的一种,现象是请求路径重复、返回 404 或路径错误。处理方式是把值改回裸地址https://taotoken.net/api。
第二,Base URL 粘贴时把 UTM 参数一起带了进去。地址栏里复制出来的链接通常带着一长串查询串,写进settings.json会让请求地址变形。UTM 只用于页面访问,不进配置文件。
第三,Key 位置还留着YOUR_API_KEY占位符没换,或者多复制了一个空格和换行。表现为稳定的 401。
第四,shell 里残留的ANTHROPIC_*环境变量覆盖了配置文件。用env | grep ANTHROPIC确认,必要时在启动前清理。
第五,用户级和项目级settings.json同时存在同名键,取值以谁为准取决于加载顺序,容易造成"改了没生效"的错觉。排障期间先只保留一份。
第六,改完不重启会话。配置文件在被读取时生效,运行中的进程不会热加载。
再看记忆文件侧的错。
第一,只扫了一份CLAUDE.md就下结论说没有循环。这个项目里可能有三四份,分布在子目录中,引用关系是跨目录形成的。用-r递归扫,并且把路径基准统一成仓库根目录,否则同名文件容易混。
第二,@行写在代码块里,被当作真实引用展开。文档里为了举例写了一段@common.md的说明,加载时可能真的去展开它。展示用的引用示例建议用行内代码包裹,或者加明显的转义标记。
第三,引用链过深但没有环。a引用b,b引用c,c引用d,一路往下一层套一层,加载时间会随深度明显上升,虽然不是死循环,体验上和"卡住"差别不大。处理思路是压缩层级,把高频共用的核心约定直接合并进一份基础文件,而不是拆成多层碎片。
第四,注释掉@行做排除法之后忘了恢复。这是原文方案四的典型后遗症:为了定位是哪一对文件构成了闭环,临时注释几行观察加载是否恢复,定位到之后必须把这批临时改动清理干净,否则会留下"引用关系看起来对、实际少了内容"的隐性缺失。
第五,循环被打破的方式不对。发现a.md和b.md互引之后,正确的改法是确定层级方向:具体规则文件引用通用规则文件,通用规则文件不反向引用具体规则文件。把a.md里的@b.md删掉、同时又在b.md里加了@a.md,等于把环换了个方向继续存在。
第六,路径写法不统一。@./common.md、@common.md、@docs/common.md混用,会让grep的结果和实际展开的路径对不上,关系图也就画不准。团队里最好统一成相对于仓库根目录的路径写法。
把这两类错分开之后,排查路径就清晰了:先确认curl能拿到正常返回,再确认settings.json里的ANTHROPIC_BASE_URL和 Key 生效且没有环境变量干扰,最后才进入CLAUDE.md的引用图分析。顺序反了,就会在引用关系上反复纠结,而真正的问题一直在通道配置里。
六、把这条排查链路固定下来
如果只想快速复现这份流程,按这个顺序走一遍:在 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys 创建 Key,把 Base URL 写成https://taotoken.net/api落进~/.claude/settings.json或项目级.claude/settings.json,具体键名和写法对照 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc 核对一遍;模型标识在模型对话页面确认(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=模型对话 ),不在配置里猜测;用curl打一次/v1/messages确认通道可用;再让 Claude Code 扫描全部CLAUDE.md输出@引用边,配合判环脚本确认闭环是否已经拆成单向层级。
排查清单可以按这个最小集合执行:递归列出所有CLAUDE.md中的@引用;把引用关系整理成"文件到文件"的边表并检查是否有环;发现环路后按"具体引用通用"的方向重建层级,只做单向;控制嵌套深度,避免无谓碎片化;引用关系复杂时用注释法缩小范围定位闭环点,定位后立刻恢复;把"引用关系必须是无环有向图"写进团队规范,在评审新增@行时顺手确认。
如果 Claude Code 是团队日常编码和 Agent 任务的主要入口,长期高频调用场景可以看 Coding Plan(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan ),先把通道稳定下来,再让这套记忆文件规范长期跑在单向无环的结构上,循环引用这类隐蔽问题就不会反复出现。