Harness Engineering

PR 堆成山怎麼辦?Matt Pocock 談 agent 時代的 Code Review

2026-10-10 ·約 22 分鐘 · — views
本文目錄

agent 讓開 PR 變得很便宜。一個 issue 丟進去,幾分鐘後就有一個 CI 全綠的 PR 等著你。可是值得 merge 的 PR 沒有跟著變便宜,團隊卡住的地方,從寫 code 搬到了那一排沒人有空看的 Pull Request。

Matt Pocock 在 2026 年 9 月 AI Engineer Paris 的演講〈Fixing the PR Bottleneck〉講的就是這個瓶頸。他把「review 太慢」拆成三個更具體的問題:這個 PR 能不能信?品質能不能控?產生 PR 的那套環境,會不會一輪比一輪好?三個都處理好,人的 review 自然就快了。

讀完精華版(2 分鐘),你會理解:

  • 為什麼 CI 全綠不代表可以 merge,agent 最常寫出哪三種假測試
  • 為什麼寫法規範應該從 AGENTS.md 搬走,交給一個獨立 context 的 review agent
  • 怎麼用「單向門 / 雙向門」決定哪些 PR 值得人花時間
  • retro 怎麼把每一次 review 學到的東西寫回環境,讓整個團隊的 agent 一起變好
  • 怎麼用 Claude Code hooks 與 GitHub Actions,讓不同開發者的 agent 走同一套流程

這篇不是 skill 安裝教學。材料是 Matt 的演講和他 skills repo v1.3 的 SKILL.md 原文,加上我自己的解讀,想回答的是:團隊要怎麼樣才敢 merge agent 開的 PR。


精華版

要解的問題做法對應的 skill / 檔案
PR 可信自動檢查做厚;用 deep module 讓測試只打介面improve-codebase-architecture、codebase-design
品質可控規範交給獨立 context 的 review agent,預設直接修code-review、CODING_STANDARDS.md
人力聚焦PR 描述標明單向門 / 雙向門與 blast radiuspr
環境進化同一句意見不寫兩次,錯誤寫回檢查與規範retro
  • 三層煞車:PR 依序經過自動檢查、自動 review、人的 review,下面兩層越厚,人那層越快。
  • 假綠燈:agent 的測試常綁住程式的寫法而非行為,CI 綠了也證明不了功能對。
  • 兩個 context window:實作 agent 的 context 已經塞滿,寫法規範交給獨立的 review agent 執行。
  • 單向門:只有 merge 後撤不回來的改動需要人細看,其他 PR 掃過就好。
  • retro:同一種錯誤出現第二次,就寫回環境,變成一條檢查或一條規範。

寫法規範該放哪? 放 CODING_STANDARDS.md,只給 review agent 讀,AGENTS.md 只放 navigation pointer。

review agent 該留言還是直接改? 預設直接 commit,自己也拿不定主意的才留言。

錯誤該寫成規範還是檢查? 機械性的錯誤寫成 lint rule 或 CI job,需要判斷的才寫進規範。


以下是完整版,按需取用。

為什麼 agent 讓 PR 變多,review 卻成了瓶頸?

agent 讓 PR 的數量暴增,但每一個 PR 仍然要有人確認能不能 merge,團隊的產能就卡在 review。

Matt 先描述了現在很流行的「軟體工廠」:工作不再全部由人發起。資料庫的慢查詢報告一進來,系統自動交給 agent 寫修正、開 PR;issue 被分類器判成 bug,agent 自動去做重現。這些觸發點都是寫死的程式,它們讓 code 越流越快。

他的比喻是,一座只有油門的工廠,最後會變成一台 slop cannon,噴出一大堆品質很差、多到沒人碰得了的 PR。所以工廠需要煞車,讓速度慢一點、品質高一點。

Matt 給煞車的理由是:code 就是 agent 工作的環境。agent 寫新 code 時會參考你現有的 code,一塊爛 code 進去,它會照著同樣的樣子生出更多爛 code。人寫的爛 code 傳播得很慢,agent 一個下午就能把一個壞模式複製到十個檔案。

所以 merge 一個 PR,影響的不只是這次的功能,還有 agent 下一輪要參考的範本。PR 的品質門檻,其實是在守 agent 未來的產出品質。

PR review 的三層煞車是什麼?

PR review 的三層煞車由下往上是自動檢查、自動 review、人的 review,Matt 把它們比喻成疊起來的蛋糕。

PR Review 的三層煞車
③ Human review
人的時間
單向門細看 雙向門掃過
② Automated review
Token
CODING_STANDARDS.md 獨立 context 直接 commit
① Automated checks
CPU
lint test type check pre-commit / CI

最下面是自動檢查:lint、測試、型別檢查、code quality 指標。它們每次跑出來的結果都一樣,花的只是 CPU。中間是自動 review,讓 agent 去看測試抓不到的東西,或者看整個 codebase 的結構,花的是 token。最上面才是人的 review,花的是工程師的時間,也是三層裡最貴的。

三層的成本差了好幾個數量級,設計方向就很清楚:能在下面攔掉的,就不要留到上面。Matt 特別提到,自動檢查很便宜,大部分 repo 用得太少,也用得不夠有創意。agent 把 bug 寫出來、被測試抓到、再花 token 修掉,這筆 token 花得很值得。

套到自己的團隊,第一件可以做的事是把 PR 現在經過哪幾層列出來。很多團隊其實只有第一層和第三層,中間是空的,所以所有 agent 產出的品質問題都直接落到人身上。

CI 全綠為什麼不代表可以 merge?agent 最常寫的三種假測試

CI 全綠不代表可以 merge,因為自動檢查本身也會說謊,所以上面兩層 review 的工作,有一大部分是測謊。他舉了三個從自己 agent 身上抓到的例子。

第一種是套套邏輯的測試(tautological test),把實作原封不動再斷言一次。程式裡寫 X 貼文的字數上限是 280,agent 寫的測試就是「預期字數上限等於 280」:

// 實作
export const X_POST_CHARACTER_LIMIT = 280;

// agent 寫的測試
expect(X_POST_CHARACTER_LIMIT).toBe(280);

這個測試沒有檢查任何行為。常數改個名字它會壞,X 哪天把上限調高它也會壞,功能明明沒問題,測試卻紅了。

第二種是結構敏感的測試。Matt 的例子是一個檢查 UI 順序的測試:它沒有把頁面渲染出來,而是把元件的原始碼檔案讀進記憶體,找到 content plan 和 videos 兩段字串,斷言 videos 出現在後面。原始碼的排版換一下,測試就壞,畫面上的順序對不對,它其實不知道。

第三種是永遠不會失敗的測試。一個用到瀏覽器 AudioContext API 的 hook,測試裡把 AudioContext 整個 stub 成假方法。真實的 AudioContext 在某些條件下會丟錯,這些情況測試永遠碰不到,最後在 production 才炸出來。

這三種測試有一個共通點,agent 都不是故意作弊,它只是照指令寫測試。問題出在測試綁住了程式的結構,沒有綁住程式的行為。這也代表,叫 agent「寫好一點的測試」沒什麼用,要從 code 的設計下手。

deep module 怎麼從設計上擋住假測試?

Matt 的解法是 deep module,出自 John Ousterhout 的《A Philosophy of Software Design》:用一個很小的介面,藏住一大塊複雜的實作。相對的是 shallow module,介面很大、函式很多,每個函式各自做的事卻很少。

如果 codebase 由 deep module 組成,大部分實作細節都藏在介面後面,測試能碰到的只有那個小介面,結構敏感的測試自然變少。工程師的工作就變成逼 agent 從介面去測,不准它把手伸進實作裡。

這部分對應兩個 skill。improve-codebase-architecture 會掃過 codebase,產出一份 HTML 報告,列出哪些地方可以合併成更深的模組,附 before / after,讓你挑一個再跟 agent 一起改。codebase-design 則定義了一組團隊共用的詞彙:locality 是相關的 code 有沒有集中在一起,改一處會不會牽連一大片;leverage 是呼叫端用一個簡單介面能拿到多少功能;seam 是可以替換實作的接縫。Matt 吐槽說,你去查怎麼組織 codebase,會找到二十種做法,而且全都叫 DDD,團隊需要的是一套大家講同一件事時用同一個詞的語言。

我覺得這段的意義在於,它把「測試品質」從 review 時才處理的問題,往前推到架構設計。介面設計得好,agent 想寫爛測試也比較難寫出來。

寫法規範該寫進 AGENTS.md,還是交給 review agent?

寫法規範應該放在 CODING_STANDARDS.md,交給獨立 context 的 review agent 執行,因為實作 agent 的 context 已經超載。

大部分團隊的做法是把寫法規範寫進 AGENTS.md 或 CLAUDE.md,讓 agent 一邊寫一邊遵守。Matt 認為這樣做錯了,理由要從 context window 來看。

實作 agent 在一個 context window 裡要做三件事:探索,找出要改的 code;修改檔案;留預算給除錯,跑自動檢查、修錯誤。它的 context 本來就是超載的。再塞一整份寫法規範進去,每件事都會做得比較差,而且它可能讀,也可能不讀。

review agent 的狀況剛好相反。它拿到的是 diff,已經知道改了哪裡,只需要做一點探索來理解周邊脈絡,不用實作也不用除錯。它的 context 很空,可以放進一大堆規範。

所以 Matt 把寫出好 code 拆成兩步:實作負責讓它能動,code review 負責讓它變好,寫法規範在第二步才套上去。他自己也說這有點讓人不舒服,因為大家都希望第一次就寫出好 code。對老派工程師來說,這就是紅燈、綠燈、重構,只是 red-green 和 refactor 分在兩個 context window 裡做。

v1.3 的 code-review skill 就是照這個想法做的。它讀 diff 和 repo 裡的 CODING_STANDARDS.md,用兩個平行的 subagent 分別審兩個軸:Standards 軸檢查有沒有違反規範,另外附一組 Fowler《Refactoring》裡的 code smell 當基線;Spec 軸檢查實作有沒有符合原始 issue。兩軸刻意分開回報,避免「完全照規範但做錯功能」和「功能對但破壞慣例」互相掩蓋。

演講裡還有一個我很認同的立場:不要把自動 review 外包。很多團隊直接用第三方的 AI review 服務,但 Matt 自己試過做一個通用的 review skill,結果不是太廣、產生一堆跟你無關的誤報,就是太窄、換個語言就不能用。他的建議是自己長出一份 coding standards,在團隊裡共用。那些寫好了卻沒人讀的文件,正好可以放進這裡,終於有一個 agent 會每次都讀它。

這個拆法讓品質變成可以調整的東西。實作 agent 寫出來的 code 不夠好,你要改的是 CODING_STANDARDS.md,不用再跟一個 context 已經爆掉的 agent 糾纏。

review agent 找到問題,該留言還是直接 commit?

Matt 的答案是預設直接 commit。很多人設定 review agent 的方式,是讓它讀完 code 在 PR 底下留言。Matt 指出,這樣 review agent 其實是在幫人類 reviewer 增加工作:人要讀完一大堆囉嗦的留言,再一則一則決定改不改。

他的主張是 reviewer 應該直接 commit,把找到的問題真的改掉。人打開 PR 時看到的,已經是整理過的成品。只有 review agent 自己也不確定的地方,才留言問人。

我讀了 v1.3 的 code-review SKILL.md,它目前的流程是把兩軸的 findings 整理成報告,並不會自己修改或 commit。「預設直接 commit」是演講提出的原則,要落到自己的流程,得在 review 步驟後面再接一段「依 findings 修正並 commit」,或者自己改寫這個 skill。

哪些 PR 一定要人看?用單向門與雙向門分流

只有 merge 之後撤不回來的單向門 PR,才需要人仔細看。前兩層做好,PR 的品質會提高,但數量還是很多,不可能每個都仔細看。Matt 借用 Amazon 的單向門 / 雙向門來分流。

雙向門是 merge 之後隨時推得回來的改動,出錯 revert 就好,大部分 PR 都是這種。他說這是當軟體工程師的福利,土木工程師做的大多是單向門,橋蓋錯了就是垮。單向門是關上就回不去的改動:昂貴的 migration、可能掉資料的操作,或者一個看起來很小的改動,不小心寄了一封信給六萬個人。單向門不一定長得很大,判斷標準只有一個,出錯之後還能不能撤回。

v1.3 新增的 pr skill 把這件事寫進 PR 描述的模板。模板有三段:

## Summary
<diagram, diff-sketch, or tree>

## Evidence
- **Before:** <screenshot/output/failing test run>
  **After:** <screenshot/output/passing test run>

## Merge Danger
**Door:** <one-way or two-way>
**Blast Radius:** <one-word description>

Summary 用 pseudocode、呼叫樹、元件樹或 diff sketch,挑最小的那種視圖講清楚這個 PR 改了什麼。這段的靈感來自 HumanLayer 的 show-me skill,Matt 在演講裡特別致謝。Evidence 放修改前後的對照,截圖最好,其次是測試和 console 輸出。Merge Danger 標明是哪一種門,以及 blast radius,也就是出錯時會波及多少使用者、資料或系統。

這個模板改變的是 reviewer 打開 PR 時的第一個問題。以前是「這個 PR 在幹嘛」,要讀完整段 diff 才知道;現在是先看最底下的 Merge Danger,雙向門、影響範圍小,掃過就好,時間全部留給單向門。

retro 怎麼讓 agent 的工作環境持續進化?

retro 把人在 review 裡發現的問題寫回 agent 的工作環境,讓同一種錯誤不會出現第二次。這是我覺得整場最值得帶走的想法。

Matt 說,團隊現在共用同一批 skill 和 steering files,大家其實是在一起幫 agent 打造工作環境,產生 code 的那套流程,應該被看得跟 code 本身一樣重要。所以人做 review 時,看的不只是這次的 code,也是在 review 產生它的系統。

他的原則是:同一句意見不要寫兩次。如果在兩個不同的 PR 裡抓到 agent 犯同一種錯,問題不在這兩個 PR,在環境。下一次應該讓環境自己擋掉這個錯。

Retro 回饋迴圈
Agent 的工作環境
AGENTS.md CODING_STANDARDS.md lint / test / CI codebase
↓
Implement實作 agent
↓
Automated checksCI
↓
Code review + commitreview agent
↓
Human review人
↓
Retro
機械性錯誤→ lint rule / hook / CI job 判斷型錯誤→ CODING_STANDARDS.md 找不到檔案→ AGENTS.md pointer

retro 就是做這件事的 skill。你把一次 agent session、一個 PR 加上它的 session,或者過去一週所有的 PR 和 review 丟給它,它會依嚴重程度列出環境可以改進的地方。SKILL.md 裡列的類別比演講講的還多:

  • Navigation:agent 花很久才找到某個資訊,就在 AGENTS.md 加一個 navigation pointer
  • Automated checks:agent 犯的錯能不能被 lint、型別、測試抓到;它會先讀 repo 既有的 check script 和 CI,已經存在卻沒接上的檢查才是 finding,而一個完全沒有 pre-commit 也沒有 CI 的 repo,本身就是一個 finding
  • Coding standards:review agent 漏抓了什麼,要新增、刪除還是釐清哪條規則
  • Global AGENTS.md:有沒有該搬去 coding standards 或自動檢查的指令
  • Tool economy:哪些 tool call 很貴,哪個 CLI 或 MCP 特別浪費 token
  • No-ops:steering files 裡有沒有寫了也不會改變 agent 行為的指令
  • Information access:agent 缺了什麼關鍵資訊,例如 dev server 的 log、第三方服務的唯讀權限

retro 會先把錯誤分類。機械性的錯誤,像固定的語法模式、禁用的 API、import 的形狀、檔案該放哪個資料夾,一律做成確定性的檢查:repo 自己 linter 的 custom rule、pre-commit hook,或一個新的 CI job,挑最便宜的那種。只有需要判斷的問題,像跨檔案的一致性、跟周圍風格是否一致,才寫進 CODING_STANDARDS.md。規則 agent 可能不讀,檢查每一次都會跑。

把這套分類放回三層煞車來看,retro 做的事情是把錯誤往下推。人在第三層發現的問題,能做成檢查的就推到第一層,不能的推到第二層。每推一次,下一次的人工 review 就少一件事要看。

為什麼 agent 環境變好會加速整個團隊?

agent 環境變好會加速整個團隊,因為環境是共用的。PR 堆成山,直覺的解法是加人 review,或者叫 reviewer 看快一點。Matt 這套做法換了一個施力點:把力氣花在產生 PR 的環境上。

一個人在 review 時發現 agent 老是寫套套邏輯的測試,跑一次 retro,加一條檢查,從那天起團隊裡每個人的 agent 都不會再寫出這種測試。一個人發現 agent 總是找不到某個設定檔,加一個 navigation pointer,所有人的 session 都省下那段探索。個人的 review 經驗,變成所有 agent 共用的環境。

這是複利。人寫 code 的年代,一個資深工程師的判斷要靠 code review 一則一則教給新人,傳播得很慢。現在判斷可以寫成檢查和規範,直接裝進每一個 agent 的工作環境裡,所有 session 立刻生效。

我覺得這也是 agentic coding 時代工程師工作重心的轉移。實作交給 agent 越多,人越應該把時間花在三件事上:做決定,守住回不去的那扇門,把每一次 review 學到的東西變成下一次的環境。

怎麼讓不同開發者的 agent 走同一套流程?

做法是讓規範跟著 repo 走:本機用 Claude Code hooks 給 agent 即時回饋,PR 上用 GitHub Actions 強制把關。

前面講的三層煞車和 retro,如果只存在某個工程師自己的習慣裡,團隊就沒有共用的環境可言。好幾個開發者各自開 coding agent 改同一個 repo 時,需要的是規範跟著 repo 走:不管誰開 session、用哪個 agent 進來,走的都是同一套流程。我覺得這是現在團隊導入 agentic coding 最值得先設計的地方。

設計之前先要分清楚,每一種機制綁得住的人不一樣:

機制綁得住誰
.claude/settings.json 的 hooks 與 plugins用 Claude Code 開這個 repo 的人
git pre-commit hook所有在本機 commit 的人與 agent
GitHub Actions + branch protection所有要 merge 的 PR

這張表決定了分工。Claude Code 的 hooks 適合當回饋,讓 agent 在自己的 loop 裡早點撞牆,少浪費一輪 PR;規範的執行要放在 CI,因為只有它不挑人、不挑工具。開發者用 Claude、用別的 agent、或者自己手寫,進到 PR 之後面對的是同一道門。

團隊共用的 Agent 開發流程
Repo 裡的環境
.claude/settings.json AGENTS.md CODING_STANDARDS.md lint rules .github/workflows
↓ clone / 開 session
① 本機 Claude Code回饋
enabledPlugins同一套 skills PostToolUse改完檔案即跑 format / lint Stop hook檢查沒過不准收工 /shipcode-review → commit → pr → 開 PR
↓ commit
② Git pre-commit人與 agent
lint / type同一組檢查
↓ pull request
③ GitHub Actions把關
checkslint / type / test,required ai-reviewcode-review,直接 commit 修正 merge-gate讀 Merge Danger
單向門 → CODEOWNERS approve 雙向門 → auto-merge
↓ merge
④ 每週 retrocron
input上週的 PR 與 review 留言 output一個改環境的 PR,人 review 後 merge

第一段在本機。repo 裡 commit 一份 .claude/settings.json,用 enabledPlugins 宣告團隊共用的 skills,PostToolUse hook 在 agent 每次改完檔案時跑 formatter 和 lint,Stop hook 在 agent 想收工時跑一次完整檢查,沒過就用 exit code 2 擋回去。團隊可以再包一個 /ship 之類的指令,把 code-review、修正並 commit、用 pr skill 寫 PR body、開 PR 串成一步。要注意的是 project settings 只會把 plugin 標成啟用,plugin 來源在外部 repo 時,每個人還是要自己裝一次,這一段沒辦法完全強制。

第二段是 git pre-commit,跑和本機 hook 同一組的 lint 與型別檢查。它的價值在於不分人和 agent,手寫的 commit 也要過。

第三段是 PR 上的 GitHub Actions,也是整個流程裡唯一的強制點。checks 跑 lint、型別、測試,設成 required。ai-review 用 claude-code-action 載入同一套 plugin 跑 code-review,有把握的問題直接 commit 修掉,不確定的才留言。這裡有一個設計細節:用預設的 GITHUB_TOKEN push 的 commit 不會觸發其他 workflow,review 不會無限循環,但修正後的 commit 也不會重跑 checks。比較穩的做法是用 GitHub App 的 token push,再讓 ai-review 遇到 bot 自己的 commit 就跳過。merge-gate 讀 PR body 的 Merge Danger 區塊,雙向門在檢查全綠後可以 auto-merge,單向門要求 CODEOWNERS approve。GitHub 原生的 branch protection 沒辦法依 PR 內容動態要求 reviewer,所以 merge-gate 要做成一個自訂的 required check。

第四段是每週一次的 retro,用 cron 觸發,讀上週的 PR 和 review 留言,產出的是一個改環境的 PR:新增 lint rule、修改 CODING_STANDARDS.md、在 AGENTS.md 加 pointer。這個 PR 一樣要人 review,因為它會改變所有人的 agent 行為。retro 原本的設計會讀本機的 session log,放到 CI 裡只拿得到 PR 和留言,Navigation、Tool economy 這類要看 session 過程的分析會做不到,這部分還是適合開發者在本機自己跑。

把四段接起來,環境本身也走 PR。規範、檢查、指令都放在 repo 裡,被 review、被 merge、有版本紀錄,跟 code 一樣演進。新成員 clone 下來的那一刻,手上的 agent 就繼承了整個團隊到目前為止累積的判斷。


結語:PR 瓶頸的解法在 review 之前。讓檢查不說謊、讓規範有人執行、讓同一句意見不必寫第二次,PR 自然變得可信,整個團隊也會跟著環境一起變快。


參考資料