
1. 多 Agent 并行编辑同一文件为什么 mtime 检测会误报ClaudeCode 内置工具并发安全问题清单里最先撞上的就是文件读写并发。你如果同时开两个 Agent 会话让它们改同一个文件大概率会遇到File has been modified since read这类报错。这个报错本身不是 bug而是 ClaudeCode 的 Edit/Write Tool 用 mtime文件修改时间做乐观检测的结果。它的逻辑很直白Agent 读文件时记下 mtime100写之前再查一次如果 mtime 变成 101就认为文件被外部改过拒绝写入。问题在于Windows 云同步、杀毒软件扫描、甚至某些编辑器的自动保存都会更新 mtime 但内容没变。这时候 ClaudeCode 会走一个 fallback如果是全量读取offset 和 limit 都没设就比对内容内容一致就放行但如果你是部分读取比如只读了 50 行就没法比对直接抛错。我实测下来这个误报在多 Agent 场景下特别容易触发。因为两个 Agent 交替读写mtime 一直在跳。更麻烦的是写写并发Agent A 和 Agent B 都读到fooA 先写入barB 后写入bazA 的修改就被覆盖了。ClaudeCode 的 FileWriteTool 里有一段注释明确写着「Please avoid async operations between here and writing to disk to preserve atomicity」但检查 mtime 和真正写入之间仍然有时间窗口没有锁保护。这就是典型的竞态条件Race Condition。两个 Agent 的读-改-写序列交错后执行的覆盖先执行的。对于需要多 Agent 协作的项目这个问题的严重性在于数据丢失是静默的你不会收到任何报错直到发现代码不对。要复现这个问题你可以用两个终端同时跑 ClaudeCode让它们编辑同一个文件。或者更可控的方式是写一个并发压测脚本用 TaoToken 统一 Key 同时发起多个请求观察文件最终状态。下面我会给出可复制的配置和复现步骤。先理解一个关键点ClaudeCode 的并发控制策略是「轻量级乐观检测 串行化执行」它没有分布式锁也没有文件锁。Tool Orchestration 层会尽量串行化工具调用但跨会话、跨 Agent 的并发它管不了。所以真正的解法不是等官方加锁而是在使用层面做隔离——比如每个 Agent 用独立的 worktree。2. TaoToken 统一 Key 接入让并发压测可复现要做并发安全排查你得先有一个稳定的、可并发调用的 API 通道。TaoToken 在这里的作用是提供统一的 Key 和 API 入口让你可以用同一个 Key 发起多个并发请求模拟多 Agent 同时操作。它的 API 地址是https://taotoken.net/api兼容 Anthropic 的接口格式ClaudeCode 可以直接对接。为什么排查并发问题需要统一 Key因为如果你用多个不同的 Key 或账号请求可能被路由到不同的后端并发行为不一致复现结果不可信。统一 Key 能保证所有并发请求走同一条通道变量可控。接入方式很简单ClaudeCode 支持通过环境变量配置 Base URL 和 API Key。你需要在 shell 里设置export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEY你的TaoToken Key如果你用的是 ClaudeCode 的配置文件方式可以写到~/.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key } }Key 的获取在控制台的 API Keys 页面地址是https://taotoken.net/console/api-keys。拿到 Key 后建议先单独验证一次请求是否通再上并发。验证命令curl https://taotoken.net/api/v1/messages \ -H x-api-key: 你的Key \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: claude-sonnet-4-20250514, max_tokens: 64, messages: [{role: user, content: ping}] }返回里有content字段就说明通道正常。这一步很重要因为后面并发压测如果报 401你要能区分是 Key 问题还是并发问题。关于模型 IDClaudeCode 里常用的有claude-sonnet-4-20250514、claude-opus-4-20250514等。你可以在模型对话页面确认当前可用的模型列表地址是https://taotoken.net/models。并发压测时建议用同一个模型 ID避免不同模型的行为差异干扰结果。配置完成后ClaudeCode 的所有工具调用都会走 TaoToken 通道。这时候你开多个会话它们共享同一个 Key但各自有独立的会话上下文。并发问题就出在它们操作同一份文件系统资源的时候。需要提醒的是TaoToken 只是通道它不解决并发安全问题。并发安全是 ClaudeCode 工具层的事。TaoToken 的价值在于让你能稳定复现问题并且在实际使用中统一 Key 便于你观察请求量、排查是哪个会话触发了异常。3. 可复制的并发压测配置与竞态复现步骤这一节给你一套可以直接跑的配置。目标是用两个并发的 ClaudeCode 会话复现写写覆盖和 TOCTOU 问题。先准备测试目录和文件mkdir -p ~/concurrency-test cd ~/concurrency-test echo function foo() { return 1; } target.js git init git add target.js git commit -m init然后写一个并发触发脚本用两个后台进程同时调用 ClaudeCode。ClaudeCode 支持非交互模式可以用-p参数直接传 prompt#!/bin/bash # concurrent-test.sh claude -p 把 target.js 里的 foo 函数改成返回 2用 Edit 工具 PID_A$! claude -p 把 target.js 里的 foo 函数改成返回 3用 Edit 工具 PID_B$! wait $PID_A wait $PID_B echo 最终文件内容 cat target.js echo git diff git diff跑这个脚本你会看到两种结果之一要么其中一个 Agent 报File has been modified since read要么两个都成功但最终只有一个修改生效另一个被覆盖。这就是写写竞态。要更精确地复现 TOCTOU你需要制造「检查通过但写入前文件被改」的窗口。可以在两个 ClaudeCode 会话之间插入一个外部修改动作# 会话 A 启动编辑 claude -p 读取 target.js 并准备修改 foo 函数 sleep 2 # 外部修改文件模拟 linter 或用户操作 sed -i s/return 1/return 99/ target.js # 会话 A 继续写入 wait这个场景下会话 A 的 mtime 检查可能通过因为它在 sed 之前读的但写入时会覆盖掉 sed 的修改。这就是 Time-of-Check-Time-of-Use 漏洞。对于多 Agent 编辑同一文件的 diff 混乱问题你可以用 worktree 隔离来对比。ClaudeCode 的 Agent Tool 支持isolation: worktree参数每个 Agent 在独立的 worktree 里操作{ isolation: worktree, run_in_background: true, prompt: 修改 target.js }用 worktree 隔离后两个 Agent 各自在自己的目录副本里改最后合并。这样就不会有写写覆盖但合并时可能有冲突需要你手动处理。压测配置里还有一个关键参数是并发数。你可以用xargs -P控制并发seq 1 5 | xargs -P 5 -I {} claude -p 在 target.js 末尾追加一行 // agent {}这会同时起 5 个 ClaudeCode 进程每个追加一行。跑完检查文件你会发现行数可能少于 5因为写写覆盖丢了更新。实测下来并发数越高丢失越明显。但要注意ClaudeCode 本身有 Tool Orchestration 串行化单会话内的工具调用是串行的只有跨会话才真正并发。所以压测必须用多个进程或多个会话。4. 验证请求与成功结果怎么确认并发问题真的发生了跑完压测你需要一套验证动作来确认问题。不能只看最终文件因为覆盖是静默的。第一步检查文件内容是否符合预期。5 个并发追加如果最终只有 3 行说明丢了 2 次更新grep -c // agent target.js第二步看 git diff 是否包含所有修改。如果 diff 里只有部分 agent 的修改说明其他修改被覆盖git diff --stat git diff | grep agent第三步检查 ClaudeCode 的日志。ClaudeCode 会把工具调用事件写到日志里你可以用CLAUDE_CODE_LOG_LEVELdebug启动观察是否有File has been modified或FILE_UNEXPECTEDLY_MODIFIED_ERRORCLAUDE_CODE_LOG_LEVELdebug claude -p 修改 target.js 21 | grep -i modified\|error第四步验证资源泄漏。并发跑多个 Agent Tool 后检查是否有残留的 worktreegit worktree list ls -la .git/worktrees/如果 Agent 崩溃或超时worktree 可能没被清理。ClaudeCode 的 AgentTool 里是 fire-and-forget 模式异步任务的异常不会传播到主线程finally块在进程崩溃时不会执行。所以 worktree 残留是真实存在的。第五步验证 Bash Tool 的子进程泄漏。跑一个会超时的命令claude -p 执行 sleep 3600超时设为 5000ms然后在另一个终端查进程ps aux | grep sleep 3600如果 sleep 进程还在说明超时后 SIGTERM 没杀掉它。ClaudeCode 的 BashTool 用treeKill发 SIGTERM但没有 SIGKILL fallback某些进程会忽略 SIGTERM。成功的结果应该是文件内容完整、git diff 包含所有修改、日志无 modified 报错、worktree 列表干净、无残留子进程。如果任何一项不满足就说明对应的并发问题存在。对于 Skill 加载的竞态你可以观察日志里是否有重复加载同一 Skill 的记录。Skill 加载是幂等的重复加载不会导致功能错误但会浪费资源。验证方式是看loadSkill被调用的次数CLAUDE_CODE_LOG_LEVELdebug claude -p 编辑 src/auth.ts 21 | grep -c loadSkill如果次数大于 Skill 数量说明有重复加载。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth并发压测过程中你会遇到几类典型报错。这一节逐个排查。401 Unauthorized最常见。先确认 Key 是否正确再确认 Base URL 是否带了/api。TaoToken 的 API 地址是https://taotoken.net/api不要写成https://taotoken.net。检查环境变量echo $ANTHROPIC_BASE_URL echo $ANTHROPIC_API_KEY如果 Key 里有特殊字符确保用引号包裹。401 也可能是 Key 过期或额度用完去控制台确认。local proxy failed这个报错通常出现在你配置了本地代理但代理没启动。ClaudeCode 会读HTTP_PROXY/HTTPS_PROXY环境变量。如果你不需要代理清掉unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy然后重新跑。注意这里说的代理是本地网络配置不是让你去用什么特殊工具只是环境变量清理。reading choices 报错这个通常出现在响应格式不符合预期时。ClaudeCode 期望 Anthropic 格式的响应如果通道返回了 OpenAI 格式就会解析失败。确认你用的是https://taotoken.net/api而不是其他路径。同时确认请求头里有anthropic-version: 2023-06-01。OAuth 相关报错如果你之前用 OAuth 登录过 ClaudeCode配置里可能残留 OAuth token和 API Key 冲突。检查~/.claude/settings.json确保没有oauthToken字段。如果有删掉只用ANTHROPIC_API_KEY。Codex auth.json 冲突如果你同时用 Codex 和 ClaudeCode~/.codex/auth.json里的配置可能被误读。确认 ClaudeCode 读的是自己的配置文件。Codex 的 auth.json 格式是{ OPENAI_API_KEY: sk-xxx, base_url: https://taotoken.net/api }ClaudeCode 不读这个文件但如果你用 CC Switch 之类的工具切换要确认切换到了正确的配置。CC Switch 配置三件套如果你用 CC Switch 管理多个通道确保 Base URL、Key、Model ID 三件套都配对# CC Switch 配置示例 [profiles.taotoken] base_url https://taotoken.net/api api_key sk-你的Key model claude-sonnet-4-20250514三件套缺一不可。只配 Base URL 不配 Key 会 401只配 Key 不配 Model 会用默认模型可能不兼容。Cline MCP 配置如果你在 Cline 里用 MCP 接 TaoToken配置在cline_mcp_settings.json{ mcpServers: { taotoken: { command: npx, args: [-y, taotoken/mcp-server], env: { TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_BASE_URL: https://taotoken.net/api } } } }注意 MCP 不要直连生产数据库只做 API 通道。并发场景特有的报错File has been modified since read是并发写写导致的解法是用 worktree 隔离。FILE_UNEXPECTEDLY_MODIFIED_ERROR是 mtime 检测失败解法是确保全量读取或者用 worktree。Circular dependency detected是 Skill 循环依赖检查 SKILL.md 里的skills字段。排查顺序建议先确认单请求能通排除 401 和通道问题再上并发观察竞态最后查资源泄漏worktree 和子进程。6. 长期编码与 Agent 协作用 Coding Plan 把并发风险降到最低如果你只是偶尔跑一下 ClaudeCode并发问题影响有限。但如果你是长期用 ClaudeCode 做项目开发或者跑多 Agent 协作并发安全就是必须处理的事。最有效的策略是 worktree 隔离。每个 Agent 在独立的 worktree 里操作物理隔离文件系统从根本上避免写写覆盖和 TOCTOU。ClaudeCode 的 Agent Tool 已经支持这个模式你只需要在调用时指定isolation: worktree。代价是合并时需要处理冲突但比静默丢数据好得多。第二个策略是串行化关键操作。对于必须改同一文件的场景不要让两个 Agent 同时改。你可以用一个队列或者用文件锁flock在外部控制flock /tmp/claude.lock claude -p 修改 target.js这样同一时间只有一个 ClaudeCode 进程能改文件。第三个策略是定期清理 worktree。ClaudeCode 有 30 天的过期清理但你可以手动清git worktree prune对于 Bash Tool 的子进程泄漏建议在脚本里加 SIGKILL fallbacktimeout -s KILL 5s your-commandtimeout命令会先发 SIGTERM超时后发 SIGKILL比 ClaudeCode 内置的 treeKill 更可靠。如果你要长期跑多 Agent 编码任务Coding Plan 提供了更稳定的资源配额和并发支持。地址是https://taotoken.net/coding-plan。它适合需要持续调用、多会话并行的场景比按次调用更划算也更容易观察并发行为。最后给你一个实用技巧在项目根目录放一个.claude/concurrency.md写明哪些文件不允许并发编辑哪些操作必须串行。ClaudeCode 会读这个文件作为上下文Agent 在操作前会参考。这不是强制锁但能减少误操作。并发安全没有银弹ClaudeCode 的设计哲学是「轻量检测 用户负责」。你要做的是理解它的边界在边界内用隔离和串行化规避风险。worktree 是你的第一道防线统一 Key 通道是你的观测窗口剩下的靠流程约束。