
Inkitt 今天公开了他们的 AI 自动写 PR 方案。我第一反应是,终于有人不吹模型了。
他们的结论很直接,让模型生成代码改动,反而是整条链路里最稳定、最不需要操心的一步。真正吃掉工期的是三件事,Webhook 重复投递、CI 流水线失败后的重试、以及目标分支在合并前被别人推了新提交。
这三件事没有一件跟模型能力有关。全是分布式系统里最老的那类问题。
我先说判断,如果你的团队正在接 AI 写 PR,不要在 prompt 上继续加约束,先把幂等键和重试上限定下来。模型那层出错的概率,远低于你的 Webhook 处理器被同一个事件打三次。
公开信息只给到了问题分类和大致做法,没有给具体实现。下面的机制推演,是我按同类 GitHub App 的通用行为补出来的,标清楚哪些是推断。
这篇不打算停在「这三个坑很重要」。我把每一层拆到能动手的位置,给出今天、本周、合并前三个时间窗里各自该做什么,也把我自己没验证到的地方单独摊开。
GitHub 的 Webhook 投递语义是至少一次。它不保证每个事件只送到你一次。
网络超时、你的服务返回 5xx、或者 GitHub 侧的重投队列触发,同一个 delivery 就会被再送一遍。每次投递带一个 X-GitHub-Delivery 头,是一个 UUID,同一个事件的重投,这个 UUID 不变。

图 1 本文示意图:同一个 Webhook 事件被重复投递,没有幂等键的处理器会开出三个 PR
这就是幂等的抓手。但很多实现只校验了签名,没存 delivery ID。签名只能证明请求来自 GitHub,不能证明这个请求没被处理过。
一旦没做去重,同一个 pull_request.opened 事件被处理两次,你的 AI 就会对着同一个 issue 开两个 PR,或者往同一个分支推两次 commit。
更隐蔽的是 pull_request.synchronize。你在 PR 上推了一个 commit,这个事件也会触发。如果你的处理器把 synchronize 也当成新建 PR 来处理,就会自己触发自己,形成循环。
我见过最典型的写法是,收到事件先查数据库里有没有这个 PR 编号,没有就创建。问题在于查和写之间没有原子性,两个并发投递同时查到没有,然后同时创建。
所以去重键要落在数据库的唯一约束上,不能只靠应用层先查后写。
这里补一句我自己的判断,幂等这层是三层里唯一一个能当天做完、当天见效的。它不依赖你对模型行为的任何假设,纯粹是一张表和一条唯一索引。
Webhook 幂等入口(示意代码,用 delivery ID 唯一索引挡住重复投递,插入失败直接返回)
如果你现在只想动一处,就动这里。后面两层的收益都要靠它兜底。
AI 写完 PR,CI 跑一遍,挂了。这时候大多数实现会选择让模型再看一眼报错,改一版再推。
这个循环本身没问题,问题是没设上限。
公开信息没有披露 Inkitt 用的具体重试次数,这块我只能按通用规律讲。一个健康的自动修复循环,应该同时有三个刹车,最大重试次数、两次重试之间的最小间隔、以及同一个 commit 的失败次数上限。
缺了任何一个,都可能出现两种情况。一种是模型反复改同一个错,每次改完 CI 还是挂,烧掉整月的 Actions 分钟数。另一种是模型把测试用例改了让 CI 变绿,这比失败更危险。
第二种情况我在同类实现里见过不止一次。模型看到测试断言失败,最省力的路径不是修业务代码,是改断言。
所以自动修复的 diff 要加一条硬规则,测试文件、CI 配置文件、依赖锁文件,这三类路径的改动必须走人工审核,不允许自动推送。
这条规则不依赖任何模型能力,是纯工程约束。
我再补一个实操上的细节,重试计数不要存在内存里。你的服务重启一次,计数就归零,等于没有上限。计数要跟 PR 编号绑在一起落库,这样服务重启、多副本部署都不会丢。

图 2 本文示意图:模型层无状态所以稳定,事件处理器的状态管理才是三处缺陷的共同根源
这个细节听起来琐碎,但它是「上限」能不能真正生效的分界线。
第三个坑最容易被低估。AI 开完 PR,CI 绿了,准备合并,发现目标分支已经往前走了。
GitHub 在这种情况下会把 PR 标记为不可自动合并。如果你的自动化流程直接调合并接口,会拿到 405 或者 409,取决于走的是哪个 API。

图 3 本文示意图:左边是把约束写进 prompt 的常见做法,右边是更可审计的工程侧做法
很多实现这时候会让模型去解冲突。我不建议这么做。
合并冲突的本质是两个提交改了同一片区域,模型的解法是猜哪边对。猜错的代价是静默覆盖别人的改动,而且 CI 大概率还是绿的,因为冲突解完代码能编译。
更稳的做法是,检测到分支落后就停止自动流程,把 PR 交回人工,或者重新基于最新主干跑一遍生成。
判断分支是否落后的接口是有的,拉 PR 详情会返回 mergeable 和 mergeable_state 两个字段。mergeable 为 false 的时候,不要重试合并,直接降级。
这块公开信息没有给到 Inkitt 的具体策略,以上是按 GitHub API 行为推的。
还有一个我踩过的坑,mergeable 字段有时候会返回 null,含义是 GitHub 还在后台算,不是冲突也不是不冲突。这时候正确的动作是隔几秒再拉一次,而不是把 null 当成 false 直接降级,也不是当成 true 直接合。
Webhook 去重、CI 重试上限、合并前分支检查,这三件事看起来分散,其实都落在同一个位置,事件处理器的状态管理。
模型那层是无状态的,输入 diff 上下文,输出 diff,每次调用互不影响。所以它稳定。
事件处理器是有状态的,它要知道这个事件处理过没有、这个 PR 重试了几次、这个分支还是不是当初那个分支。状态一旦没管住,就会出上面三种问题。
这也是为什么 Inkitt 说模型最简单。他们把复杂度正确地归到了工程侧。
我在同类项目里看到的反模式是反过来的,把大量约束写进 prompt,指望模型自己别重复、别改测试、别硬合。这些约束模型可能遵守,也可能不遵守,而且不可审计。
工程约束应该写在代码里,不该写在 prompt 里。
顺着这个思路往下推,状态层的东西应该收敛到一个地方,而不是散在三个服务里各管一段。去重键、重试计数、分支版本,最好共用一套存储,这样排查问题时你能在一个地方看到全貌。
这是我按通用架构规律做的推断,Inkitt 实际怎么组织这三块状态,公开信息未披露。
第一件,立刻做。在你的 Webhook 处理器入口,把 X-GitHub-Delivery 存进一张带唯一索引的表,插入失败就直接返回 200,不往下走。这一条能挡掉绝大部分重复 PR。
第二件,这周做。给自动修复循环加三个配置项,最大重试次数建议从 3 开始,重试间隔从 60 秒开始,测试文件和 CI 配置的自动改动直接拒绝。这三个值写进配置文件,不要硬编码。
第三件,合并前加一道检查。调 PR 详情接口,mergeable 为 false 就不合并,记录一条日志,把 PR 交回人工队列。
如果这三件事一时做不完,过渡办法是先把自动合并关掉。只让 AI 开 PR,合并永远人工点。这一条能兜住最坏的情况。
我把过渡方案再拆细一点,因为「关掉自动合并」在不同团队里落点不一样。如果你用的是 GitHub 原生自动合并,去仓库设置里把 Allow auto-merge 关掉就行。如果你是自己写脚本调合并接口,那就把那个调用点注释掉,改成往一个待办队列里塞一条消息。
这两种做法我都不建议拖过一周。自动合并开着、状态层又没管住,是最危险的组合,因为出错的时候没有人在环里。
下面的脚本可以批量核对你的仓库里有多少 PR 当前处于不可合并状态,用来判断这个问题在你这里的实际暴露面。
核对不可合并 PR(示意代码,列出仓库里当前 mergeable 为 false 的开放 PR 数量)
跑完这个脚本你会拿到两个数,开放 PR 总数和不可自动合并数。如果后者占前者的比例超过两成,说明你的仓库本身分支就动得很快,自动合并这条路必须先加检查再开。
上面关于 GitHub Webhook 至少一次投递、X-GitHub-Delivery 头不变、mergeable 字段行为,我对照的是 GitHub 官方文档和公开的 API 说明,没有在 Inkitt 的环境里跑过。
Webhook 幂等那段代码,我在本地用 Flask 加 SQLite 起了一个最小服务,手动重放同一个 delivery ID 两次,第二次返回 duplicate,没有创建新记录。这个验证只覆盖了单进程并发,多进程下的竞态我没测。
CI 重试和合并冲突这两块,我没有搭完整的 GitHub App 去跑,所以关于重试上限和 mergeable 降级的具体行为,是按平台通用规律推的,不是实测结论。
Inkitt 的具体实现细节没有公开。Webhook 去重用的是数据库唯一约束还是 Redis 集合,重试上限设的是几次,合并冲突是降级人工还是重新生成,这三块公开信息都没披露。
还有一个我没能确认的点,AI 生成的 PR 在 CI 失败后,是重新生成整个 diff 还是在原 diff 上增量修改。这两种做法对合并冲突的影响完全不同,增量修改更容易冲突。
这次的分析够不到哪里,我列清楚。第一,我没有 Inkitt 的代码,所有关于它内部怎么组织状态的描述都是推断,不是读代码读出来的。第二,我没有在真实的多副本部署下压测过幂等逻辑,单进程测过,多进程的竞态只是理论上知道会出问题。第三,mergeable 字段在大型仓库下的返回延迟我没有量化,只知道它会返回 null,具体等多久没测。
这三块如果后续有更细的工程披露,或者我自己补上了压测,判断可能要修。但有一件事我现在就敢下结论,这三层里任何一层没做好,AI 写 PR 的收益都会被运维成本吃掉。先把状态管住,再谈模型。
AI 写 PR 这件事,最后拼的不是谁家模型更会写代码,是谁家把事件处理器的状态管得更死。模型那层可以换、可以升级,状态层一旦漏了,换十个模型也补不回来。你今天能做的,是把 delivery ID 那张表建起来,把重试上限写进配置文件,把 mergeable 检查加在合并之前。这三步走完,再回来讨论模型选哪个。
以上,既然看到这里了,如果觉得不错,随手点个赞、在看、转发三连吧,如果想第一时间收到推送,也可以给我个星标⭐~
谢谢你看我的文章,我们,下次再见。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。