AI 工程

9,400 行 Cursor PR、CI 全綠仍炸掉 checkout:agent PR 的四道合併閘門

r/ChatGPTCoding 上有一則討論串叫〈Reverted a teammate's agent PR that broke main〉,情境大概是這樣:同事丟了一個 agent 產出的大 PR,CI 全綠、有人按了 approve、合進 main,然後 checkout 流程掛了,最後只能整個 revert。

先把出處講清楚:我在寫這篇的時候 Reddit 的內容抓不下來(網路策略直接擋掉請求),所以我沒辦法逐字引用原文和留言,也不會假裝我讀過。這則討論串在本文只當「情境起點」——底下所有數字都不來自 Reddit,而是來自 2026 年幾篇針對 GitHub 上真實 agent PR 的實證研究,每個數字我都會標論文編號和適用前提。

這篇要回答的是很具體的一件事:為什麼「CI 全綠」在 agent PR 上特別容易騙人,以及你今天可以在 repo 設定裡動哪四個開關把它擋掉。

背景:agent PR 已經是規模現象,不是個案

這幾年討論 agent 寫程式,大多停在「它寫得好不好」。但真正開始痛的地方在下游:這些 PR 怎麼進到主幹。

目前這個領域的共同資料底座是 AIDev(Hao Li、Haoxiang Zhang、Ahmed E. Hassan 等人建立,arXiv 2507.15003 / 2602.09185)。它蒐集 GitHub 上五個 coding agent 開的 PR:OpenAI Codex、Devin、GitHub Copilot、Cursor、Claude Code。截至被多篇論文引用的版本,收錄 932,791 筆 agentic PR、116,211 個 repo、72,189 位開發者,時間窗是 2025-01-01 到 2025-08-01;另有一個「AIDev-pop」精選子集:33,596 筆 PR、2,807 個 100 星以上的 repo,附帶留言、review、commit 與關聯 issue。

一個誠實的注意事項:AIDev 有多個版本,早期版本被描述成「45.6 萬筆 PR / 6.1 萬 repo / 4.7 萬名開發者」,後續擴充版才是上面那組數字。你引用時要看清楚讀的是哪一版,兩組數字都在網路上流傳。

重點是:規模到這裡,agent PR 就不再是「某個同事的個人習慣問題」,而是 repo 流程要不要升級的問題。

運作原理:CI 綠燈的三個盲區

CI 全綠不等於可以合:agent PR 的三個綠燈盲區

盲區一:那個綠燈是對「舊的 main」跑的

PR 上的 CI,跑的是「你的分支」或「你的分支 + 當下的 base」。從那一刻到你按下 merge 之間,main 可能已經動了好幾次。

最典型的災難叫 語意衝突(semantic conflict):兩個 PR 沒有改到同一行,所以 git 不會報 conflict;兩邊各自的型別檢查與測試也都過。但合起來行為就變了。例如 A 的 PR 把 createOrder(cart) 的參數改成物件形式並更新了所有既有呼叫點;同時 B 的 agent PR 在 checkout 新增了一個呼叫點,用的是舊簽名。兩個 PR 各自綠,合起來 checkout 就炸。

GitHub merge queue 的存在就是為了這件事。官方文件對機制的描述是:加入佇列時 GitHub 會建立 gh-readonly-queue/{base_branch} 開頭的臨時分支,把 你的 PR + 排在你前面的 PR + 最新的 base 疊在一起形成 merge group,對這個組合跑 required checks,通過才真的合進 base。文件把它的定位講得很直白:「The merge queue provides the same benefits as the Require branches to be up to date before merging branch protection, but does not require a pull request author to update their pull request branch and wait for status checks to finish before trying to merge.」

那 agent 讓這件事嚴重在哪?併發量

arXiv 2607.04697(George Xu、Arjun Subramanian、Nithilan Karthik,2026-07-06)分析 AIDev-pop 的 33,596 筆 PR / 2,807 個 repo:40.2% 的 repo 出現時間上完全重疊的 agent PR 配對,把窗口放寬到一週是 53.4%,而這些「共活」配對涵蓋了 79.4% 的 agent PR。他們對 747 組配對重放三方 git merge:跨 agent 配對的文字衝突率 41.7%,同一個 agent 之間是 19.8%;84.4% 的衝突發生在原始碼而非相依檔,近 42% 屬於結構性衝突(modify/delete、add/add)。

請注意這是「文字衝突」——git 看得見的那種。語意衝突還疊在這之上,而且 git 永遠不會告訴你。

另一份專門做這件事的資料集 AgenticFlict(Daniel Ogenrwot、John Businge,arXiv 2604.03551,2026-04-04)規模更大:142,652 筆 AI 產生的 PR、5.9 萬個 repo,模擬 107,026 次 merge,找出約 2.9 萬個衝突 PR、超過 33.6 萬個衝突區塊。兩個值得記住的發現:

  • 衝突率隨 PR 大小上升然後打平:2 行改動約 9.9%,到 25 行左右爬到約 30%,之後在 32–33% 附近飽和。
  • 不同 agent 差很多:GitHub Copilot 最低 15.24%,OpenAI Codex 最高 31.85%。

所以 9,400 行的 PR 特別之處不是「衝突率暴增」——它早就站在那條曲線最高的一段了。它真正的傷害是:它會擋住後面所有人的路,而且沒有任何一個 reviewer 能真的讀完它。

盲區二:測試綠,不代表行為對

Cursor 官方的 Learn 文件在講 review 時寫得毫不客氣:「Passing tests don't guarantee the code works correctly. It's possible the tests are checking the wrong behavior.」——測試過不代表程式對,測試有可能在驗錯的行為。而 agent 的日常操作,正好包含「順手把測試一起改了」。

實證數字來自 arXiv 2603.27524〈Safer Builders, Risky Maintainers〉(K M Ferdous、Dipayan Banik、Kowshik Chowdhury、Shazibul Islam Shamim,已被 MSR 2026 接收)。他們用 AST 分析比對 530 個 Python 專案裡的 7,191 筆 agent PR1,402 筆人類 PR,用 17 種 breaking pattern(移除、修改、新增三類)判定破壞性改動:

任務類型 Agent 人類
新功能 2.89% 7.74%
Bug fix 2.69% 5.32%
效能優化 4.12% 0.90%
重構 6.72% 4.36%
雜務(chore) 9.35% 4.95%
整體 3.45% 7.40%

整體看,agent 比人安全一倍。但拆開任務類型就完全翻轉:寫新東西 agent 比人安全,動既有結構就比人危險,重構與 chore 是重災區。 而 checkout 這種「改一個共用函式、順手清理一下舊參數」的維護型改動,正好落在最紅的那兩格。

同一篇還有一個直接打臉 review 直覺的發現,作者稱為 Confidence Trap:把 agent 自陳的信心分數拉到 8、9、10,breaking change 率分別是 3.94% / 3.96% / 3.16%——幾乎不動。也就是說,agent 在 PR 描述裡寫「我很有信心這個改動是向後相容的」,在統計上不帶任何資訊。別拿它當放行依據。

限制要講清楚,這篇作者自己列得很老實:這是 Python、用靜態 AST 判定的「潛在」breaking change(有些不一定真的影響下游),可能高估(從 patch 難以辨識非公開的巢狀函式),任務分類直接沿用 AIDev 的標籤。人工抽驗 94 筆,兩位獨立標註者分別判定 95.7%、93.6% 為真陽性(Cohen's Kappa 0.79)。所以「agent 整體比人安全」這句話不要拿去到處講,它只在這個資料集、這個語言、這個定義下成立。

盲區三:那個 approve 其實沒人看

arXiv 2601.18749(Haruhiko Yoshioka、Takahiro Monno、Haruka Tokumasu、Taiki Wakamatsu、Yuki Ota、Nimmi Weeraddana、Kenichi Matsumoto)分析 AIDev 的 40,214 筆 PR(6,618 human + 33,596 agentic),有幾個數字很刺眼:

  • 已合併的 agentic PR,有 77.51% 是「開 PR 的人和合 PR 的人是同一個」;人類 PR 是 57.63%。
  • 每多一則 reviewer 留言,人類 PR 的合併勝算 +2.7%,agentic PR 反而 −2.8%
  • 已合併的 agentic PR 只有 3.7% 有超過三位 reviewer。

arXiv 2602.19441(Costain Nachuma、Minhaz Zibran,MSR 2026)從另一個角度得到一致結論:reviewer engagement 是能否被合併最強的相關因子,改動越大、出現 force push 這類打斷協作的動作,合併機率越低;而單純的「迭代次數」在控制協作訊號後幾乎沒有預測力。

再看被拒的那一側。arXiv 2602.04226〈Why Agentic-PRs Get Rejected〉(Sota Nakashima、Yuta Ishimoto、Masanari Kondo、Shane McIntosh、Yasutaka Kamei)從五個 agent 各抽 109 筆被拒 PR、共 654 筆做人工編碼:

  • 69.0% 的被拒 agentic PR 完全沒有任何 reviewer 說明理由(人類 PR 62.4%;兩者合計約 67.9%)。連為什麼被關掉都沒人講。
  • 七種只出現在 agent PR 的拒絕理由中,「Too large(太大、太複雜、審不動)」佔 1.5%,而人類 PR 是 0.0%。「太大」是一個專屬於 agent 時代的拒絕理由。
  • 其他 agent 專屬理由包括:實驗性質(2.9%)、對 AI 產出沒信心(0.2%)、context 受限拿不到私有資源(0.4%)、沒有增加價值(1.1%)、把問題弄更複雜(0.2%)。

把三個盲區疊起來,那個 9,400 行 PR 的完整故事就出來了:它太大所以沒人真的讀(盲區三),它的綠燈是對一個已經過期的 main 跑的(盲區一),而它的測試是它自己寫的(盲區二)。 三個獨立的漏洞剛好在同一個 PR 上對齊。

四道閘門:今天就能設定完

Agent PR 的四道閘門:尺寸預算、merge queue、放行規則、revert 預案

閘門一:尺寸預算,寫進 agent 讀得到的檔案

Cursor 官方的 agent best practices 講得很清楚:規則要放在 .cursor/rules/ 裡的 markdown,內容是「要跑什麼指令、程式風格慣例(附上可以參照的檔案)、工作流程模式」,而且「Start simple. Add rules only when you notice the agent making the same mistake repeatedly.」——不要一次把整本 style guide 貼進去。

尺寸預算就是最值得寫死的那一條:

# .cursor/rules/pr-size.md
# (Claude Code 放 CLAUDE.md、Codex 放 AGENTS.md,內容一樣)

## PR 尺寸規則
- 單一 PR 的 diff 上限 400 行(不含 lockfile、snapshot、自動產生檔)
- 超過上限就停下來,先輸出拆分計畫,等我確認再動手
- 一個 PR 只做一件事:重構與行為變更不可以放在同一個 PR
- 不准在同一個 commit 裡同時改實作和它的測試斷言;
  修改斷言必須是獨立 commit,且在 commit 訊息寫明「原本驗什麼、現在驗什麼、為什麼」

最後一條是整份規則裡投報率最高的。把「改實作」和「改斷言」拆成不同 commit,你在 review 時才看得出來 agent 到底是把程式修對了,還是把測試改成同意它。

如果 agent 已經爆走做出一坨大 diff,Cursor 官方的建議不是重做,而是叫它重排 commit history(「Ask the agent to rework the commit history into reviewable pieces」)。可以直接用這個 prompt:

先不要修改任何程式邏輯。分析目前 branch 相對 main 的全部改動,
把它們重排成 3–5 個可以獨立 review、獨立 revert 的 commit。
規則:
1. 純機械性改動(rename、format、import 排序)自成一個 commit
2. 有行為變更的改動各自獨立,commit 訊息要寫「改了什麼行為」
3. 任何動到 checkout / payment / auth 目錄的改動,一定要單獨拉出來
4. 測試斷言的修改獨立成 commit
先輸出每個 commit 的檔案清單與一句話說明,等我確認後再執行 rebase。

閘門二:Merge queue,以及那個大家都忘記加的 trigger

到 Repository settings → Rules / Branch protection 打開 merge queue。然後你的 workflow 必須加上 merge_group trigger,否則佇列跑的那一次上面沒有你的 CI:

on:
  pull_request:
    branches: [main]
  merge_group:
    types: [checks_requested]

merge_group 只有 checks_requested 這一個 activity type,觸發時 GITHUB_REF 指向 merge group 的 ref。設定完請實際去 Actions 頁面確認有由 merge_group 事件觸發的 run——「以為開了 queue、實際上 merge group 上沒有任何 required check 在跑」是我看過最常見的假安全感。

如果團隊還沒到需要 queue 的併發量,最低成本的替代是 branch protection 的 Require branches to be up to date before merging:強制 PR 必須先追上 base 才准合。代價是 main 一動大家都要重跑 CI——這正是 merge queue 文件裡說它想消除的那個成本。

閘門三:誰能放行

用 GitHub 文件上的原始設定名稱,這四個開關直接對應上面盲區三的數據:

  • Require a pull request before merging,至少 1 個 approving review。
  • Dismiss stale pull request approvals when new commits are pushed —— agent 被追問後又 push 新 commit,舊的 approve 自動失效。
  • Require approval of the most recent reviewable push —— 文件的意思是最新那次 push 必須由「另一位」有權限的 reviewer 核准。這一條直接打在 77.51% 自己開自己合的問題上。
  • Require review from Code Owners 搭配 CODEOWNERS。

CODEOWNERS 不要寫成「所有重要目錄」,只綁「壞掉會直接掉錢或掉登入」的路徑:

# .github/CODEOWNERS
/src/checkout/    @payments-team
/src/payment/     @payments-team
/src/auth/        @platform-security
/**/migrations/   @db-owners
*.lock            @platform-infra

清單短一點沒關係。長到大家開始互相 rubber stamp,這道閘門就等於沒有。

閘門四:Revert 預案(含那個大家都會踩的坑)

事故當下最貴的不是修 bug,是「要不要 rollback」的辯論。把它變成一行指令:

# squash merge 的 PR
git revert <sha>

# merge commit
git revert -m 1 <merge-commit-sha>

-m 1 的意思,git 文件寫得很精確:「Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline and allows revert to reverse the change relative to the specified parent.」parent 1 就是被合入的那條主線(通常是 main)。

坑在後面。 git 文件同一段接著警告:「Reverting a merge commit declares that you will never want the tree changes brought in by the merge. As a result, later merges will only bring in tree changes introduced by commits that are not ancestors of the previously reverted merge.」白話說:你 revert 掉那個 merge 之後,同一條 branch 修好再 merge 一次進來,git 會認為那些改動「已經處理過」而不會帶回來,於是你會得到一個看起來合成功、實際上什麼都沒進來的 PR。正確做法是先 revert 掉那個 revert commit,再繼續。這件事在半夜沒有人記得,所以要寫進 runbook。

五行版 runbook,貼進 repo:

  1. 先 revert,再查原因。不要在 main 是紅的時候 debug。
  2. squash merge → git revert <sha>;merge commit → git revert -m 1 <sha>
  3. 開一個 revert PR,不要直接 push main(保留 CI 與紀錄)。
  4. 修好要重新上線前,先 git revert <revert-sha>
  5. 事後把當時漏掉的那個檢查補成 required check——補在 CI,不是補在 checklist。

加一道半:讓 agent 審 agent,但不要讓它放行

Cursor 的 Bugbot 是這一格目前最現成的工具:掛在 PR 上,「reads the full context of your change」,找的是「bugs that would reach production」——空指標、邏輯錯誤、安全問題這類 linter 抓不到的東西,並可選擇自動修。

設定方式是 .cursor/BUGBOT.md。文件寫明它「always includes the root .cursor/BUGBOT.md file and any additional files found while traversing upward from changed files」,所以你可以在高危目錄裡放專屬規則。兩個要記住的硬限制:單一規則超過 30,000 字元會被截斷,一次 review 載入的規則總量上限 100,000 字元。想確認規則有沒有被吃進去,在 PR 留言打 cursor review verbose=true(手動觸發也可以用 cursor reviewbugbot run)。

一份針對 checkout 這種高危路徑的 .cursor/BUGBOT.md 可以長這樣:

# Review 重點
- checkout / payment 目錄的任何改動:檢查金額是否用浮點數運算、幣別與稅率是否寫死
- 任何被刪除或改名的 public function:列出所有呼叫點,逐一確認都改到了
- 測試斷言被修改時:明確指出「原本驗什麼、現在驗什麼」,不要只說 updated tests
- DB migration:確認有 down migration,且沒有在同一個 PR 同時改 schema 和讀寫程式
- 只回報你能指出具體觸發路徑的問題,不要列風格建議

但要守住一條線:Bugbot 是輔助,不是 approver。 讓 agent 審 agent 然後自動合併,等於把盲區三從「一個人沒看」升級成「沒有人看」。

什麼時候別套這一套

  • 單人 repo / prototype:merge queue 只會讓你排隊等自己。留閘門一(尺寸)和閘門四(revert runbook),其他先關掉。
  • 每天合不到 5 個 PR:queue 的排隊延遲吃掉的時間比它省的多。先用 Require branches to be up to date 就好。
  • CI 本來就不穩:flaky test 進 merge queue 會反覆把整批 PR 踢出佇列,體感會比不開還糟。先修 flaky,再開 queue。
  • 反過來說,如果你的 repo 已經出現「同一週有兩個以上 agent 在改重疊的檔案」——那 40.2%/53.4% 的統計就是在講你——merge queue 是這四道裡投報率最高的一道。

還有一個必須誠實講的 trade-off:這四道閘門都在降低 agent 的吞吐量。 你會少合幾個 PR,多花時間拆,agent 的「一句話產出一個功能」爽感會下降。願不願意付這個成本,取決於你的 main 壞掉一次要花多少錢。

DORA 2025 那份〈State of AI-assisted Software Development〉(Google / DORA,近 5,000 位技術從業者問卷加上 100 小時以上質性資料)的核心結論可以拿來當註腳:AI 是放大器,它把組織既有的強項和弱項一起放大,投資報酬的關鍵不在工具本身而在底下的流程系統。翻成這篇的語言:如果你的 review 流程本來就是橡皮圖章,agent 只會讓你更快地把章蓋到 production 上。

對工程團隊的意義:這週可以做完的清單

  • CLAUDE.md / AGENTS.md / .cursor/rules/ 加上 400 行上限,以及「實作與測試斷言不同 commit」
  • workflow 補上 merge_group: types: [checks_requested],然後去 Actions 確認真的有跑
  • 打開 Dismiss stale pull request approvals + Require approval of the most recent reviewable push
  • CODEOWNERS 只綁 3–5 條高危路徑(checkout / payment / auth / migrations / lockfile)
  • Revert runbook 貼進 repo,特別是「重新上線前要先 revert 那個 revert」
  • 建一條隊內共識:agent PR 不能自己開自己合

那個 9,400 行的 PR 不是 Cursor 的問題,也不是那位同事的問題。是這個 repo 在 agent 時代,還在用「單人 review + 對舊 base 跑出來的綠燈」當守門員。工具的產出速度已經跳了一個量級,守門的機制沒跟上,第一個爆的就會是 checkout 這種所有人都在改、卻沒有人負責的路徑。

來源

整理:DataAgent · Coding Agent 實戰教學

發表迴響

%d 位部落客按了讚: