有一阵子,我的工作节奏是这样的:Agent 在左边的终端里改代码,我在右边编辑器里改同一个文件。两边都觉得自己拥有这个文件。直到一次我按下保存,屏幕弹出一句 File has been modified by another process,我才发现,我十分钟的改动被覆盖了。

这事让我困惑了很久。按理说,并发编辑是个老问题。数据库有事务,Git 有合并,Google Docs 有实时协同。为什么一个如此智能的 Agent,却像在裸奔?
后来我去翻各种编程 Agent 的设计思路,才慢慢想通:大多数 Agent 处理并发冲突的方式,比你以为的原始得多。
文件系统没有“锁”,也没有“事务”
先看一个基本事实:Agent 和你修改的是同一个文件系统的同一个文件。文件系统提供的基本操作是 读一个文件,写一个文件,但它不知道“编辑事务”是什么。它没有数据库里那种行级锁,也没有给文件加版本号。文件锁可以做到独占访问,但大多数编程 Agent 根本不用它,因为锁会挡住用户的正常编辑,而且容易死锁。
因此,当两个人(人或 AI)同时写同一个文件时,最终的输赢取决于写入顺序:后写的覆盖先写的。这里没有协商,没有合并,只有覆盖。
Agent 分两类,冲突风险完全不同
不是所有 Agent 都有同样的冲突问题。我在实践中观察到,编程 Agent 大体分三类:
| 类型 | 工作方式 | 冲突风险 | 典型代表 |
|---|---|---|---|
| 建议型 | 生成代码块或补丁,由用户手动接受 | 低 —— 用户有最终决定权 | GitHub Copilot |
| 直接写入型 | 自己读文件、改文件、写磁盘 | 高 —— 可能覆盖用户未保存的缓冲区 | Cline、Claude Code、Codex CLI |
| IDE 协作型 | Agent 的修改作为编辑器内变更展示,用户确认后应用 | 中 —— 借助编辑器的 diff 和 undo 机制降低风险 | Cursor 的 Apply |
最容易引起你问“怎么办”的就是第二类,直接写入型。它有能力在你不知情的时候改变磁盘上的文件。
直接写入型 Agent 做了哪些检查
我最早以为,Agent 会像 Git 一样先记录现状,写完以后做一次 merge。后来看了一些直接写入型 Agent 的输出日志和报错提示,才发现真实做法要朴素得多。它采用的是乐观并发控制:假设用户在 Agent 运行期间不会碰文件,只在最后写入前做一次校验。典型流程是这样的:
- Agent 读取文件内容,并记录下当前内容的哈希(或修改时间)。
- Agent 基于这份快照生成新内容。这个过程可能需要几秒到几分钟。
- 写入前,Agent 重新读取一次文件,并与之前记录的哈希做比较。
- 如果哈希一致,Agent 认为没人动过,直接写入新内容。
- 如果哈希不一致,Agent 报错、跳过,或提示你手动处理。
这个模式和数据库里的 乐观并发控制很像:不提前加锁,只在提交时检查版本。听上去还挺合理?但问题是,文件系统不像数据库那样维护版本号。Agent 只能用内容哈希或 mtime 来近似“版本”。而这两个“版本号”自己都不可靠。
为什么检查过了,你还是会丢代码
我遇到的情况就是典型例子。Agent 在文件系统级别写文件,而我改的内容还停在我的编辑器缓冲区里,没有落到磁盘。Agent 读取磁盘时看不到我的改动,校验哈希时也看不到,等它写完,我的缓冲区还停留在旧版本。然后我按下保存——我把自己那版内容写回磁盘,又把 Agent 的改动覆盖了。
换句话说,冲突不是同时发生,而是双方在完全不同的层级里工作:Agent 在磁盘上写,人在编辑器内存里改。Agent 并不知道你正在编辑一个未保存的文档,而你的编辑器也不知道磁盘在 Agent 出手的那一刻已经变了。于是两边互相覆盖,而且都不知道自己干了什么。
如果编辑器足够智能,你可能会看到“文件已在外部被修改”的提示,但大多数情况下你只会在保存之后发现不对。这已经晚了。
终极解法:让 Agent 和用户共享同一个“文档状态”
你一定想问,为什么不用协同编辑技术?Google Docs 里两个人同时打字也不会互相覆盖。这是因为它们用了 OT 或 CRDT 这类算法:每个参与者编辑的不是磁盘上的文件,而是一个共享的文档模型,所有改动通过协议同步,并在合并时解决冲突。
如果 Agent 也接入编辑器的文档模型,而不是直接改文件系统,它就能像另一个协作者一样产生可合并的操作。这正是 IDE 协作型 Agent 做的事。但直接写入型 Agent 绕过了这一层。它面对的只是一个路径和一个 write_file 函数,它根本不知道自己写的是不是最新的。
所以,CRDT 和 OT 并没有过时,只是大多数 Agent 没有采用它们。它们把冲突处理的难题留给了 Git 和你的保存键。
给同时编辑场景的三条实用建议
理解原理之后,我现在的习惯是这样的:
- 让 Agent 只负责你当前不碰的目录或模块。给它明确的任务范围,别让它大范围“重构”。
- Agent 运行时,不要打开它正在改的文件。如果它跑得久,就切到别的分支,或者在旁边等它完成再检查 diff。
- 真被覆盖了,不要慌。用
git reflog找回之前的提交,或者用编辑器的本地历史(Local History)找回未提交的内容。你真正的保镖不是 Agent 的智能,而是 Git 的多分支和你的撤销缓存。
这三条只能降低概率,不能消灭冲突。如果你的工作流里“人机同时编辑同一个文件”是家常便饭,那就真的需要考虑换成 IDE 协作型 Agent,或者让 Agent 把修改写到独立文件,再通过人工 review 合并。
为什么 Git 不能实时帮你自动合并?
Git 只在你执行 commit、merge 时对比内容。Agent 直接写文件时,Git 根本不知道。你后来 commit 时,工作区里可能已经混入了两个人的改动,Git 只能基于行级 diff 做合并,而真实意图已经丢失。所以 Git 是事后的救援,不是事中的保险。
最后说句实话
Agent 的冲突处理能力,远没有你想象中那么智能。它不是面向并发设计的,它是面向“单写者”设计的。它把用户当成一个不存在的旁观者,把文件系统当成一块只能由它涂改的画布。这种设计让 Agent 可以脱离编辑器独立运行,但也让“用户和 Agent 同时编辑同一文件”成为一个不可能完全解决的问题。
未来,随着 Agent 深度集成进 IDE,并采用 CRDT 这类协同模型,冲突会减少。但在那之前,请把 Git 提交做勤一点,把重要改动尽早保存。因为等到两个“作者”同时按下最后一个键的时候,赢的是最后落盘的那个人,而不是改得对的那个人。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/786.html