Agent Distraction:一行過期 TODO 讓 coding agent 多花 77% token,三層防線手把手清乾淨
Google 分析數億步 agent trace 發現:agent 讀到帶 bug 編號的 TODO 後,追的支線有 83% 沒人要求、41% 指向已關閉的票,這類 session 的步數多 70%。本文拆解機制與限制,並給出清理腳本、CI 檢查與 AGENTS.md 範圍規則。
你叫 coding agent 修一個登入頁的 CSS,它卻花了十幾步去查一張兩年前就關掉的 bug 票。你看 log 才發現,它讀檔時剛好掃到一行 // TODO(bug/12345): handle edge case。
人看到這行會直接略過,agent 卻會當成一項任務。Google 工程師 Sam Saccone(Senior Staff Software Engineer)和 Madhuri Garikapati(Senior Staff Data Scientist)在 2026-11-06 的 Google Developers Blog 發了〈Agent Distraction〉,開頭第一句就講這件事:「What is easy for humans to skim past in code, agents see as a call to action.」
這篇把那份研究拆開講:它量了什麼、數字的意思和限制,再整理成一套能照做的清理流程、CI 檢查和 AGENTS.md 寫法。不管你用 Claude Code、Codex、Cursor、Cline 還是 Aider,這些做法都適用,因為問題出在 repo,跟用哪個 agent 無關。
一、為什麼這件事值得你花半小時處理
我們調 coding agent 的時候,大多把力氣放在 prompt、模型和工具設定上,很少有人去管 agent 會讀到什麼。可是 agent 做規劃和研究時會大量讀檔,讀到的內容全都會進 context。
原文的說法是:過時的規格、早就解掉的 bug、跟不上程式碼的文件,會被 agent「unintentionally ingested as context」,結果把它帶錯方向。代價分兩種:
- 最好的情況:浪費 token、步數和時間。
- 最壞的情況:agent 拿錯的資訊當依據,寫出錯的邏輯、做出錯的決定。
人類工程師有「這段很舊,別理它」的直覺,agent 沒有。對 agent 來說,repo 裡每一行字都是同樣可信的指令來源。原文說「agent friendly codebase」不能只顧常走的那條路徑,平常沒人去看的角落也會讓 agent 付出成本。
二、Google 怎麼量「分心」
這份研究不是在實驗室跑 benchmark,用的是 Google 內部的真實使用資料:
- 來源是「thousands of engineers」、「hundreds of millions of individual agent steps」的分散式 trace,以及數百萬個讀檔 session。
- 他們選了一個好量的指標:帶 bug 編號的行內註解,也就是
TODO(bug/...)和FIXME(bug/...)。
為什麼選它?因為 bug 被修掉或放棄之後,這種註解常常留在原地,而它指向的 bug 票才是權威紀錄。所以每次 agent 讀到一則這種 TODO,研究方都能拿去比對兩件事:
- 這個 bug 現在是什麼狀態(開著、修好、不修、重複)。
- 使用者的 prompt 有沒有提到這個 bug。
兩個都比對完,就能把「agent 被一行舊註解帶去做沒人要它做的事」量化出來。
要先講清楚範圍:這份研究只量了帶 bug 編號的 TODO/FIXME。過時的設計文件、死碼、舊的 AGENTS.md、測試 fixture 這些東西,文章都沒量。它們很可能有一樣的問題,但目前沒有數據,下面提到時我會標成推論。
三、運作原理:一行註解怎麼把 agent 帶偏

照原文的數字,事情的經過可以拆成四步:
第 1 步:碰到。 在數百萬個讀檔 session 裡,約 13%(原文說「nearly 1 in 7」)讀到了帶 bug 編號的 TODO 或 FIXME。在大型 codebase 裡這不稀奇,agent 找相關程式碼時順手打開的檔案裡就常有。
第 2 步:上鉤。 大多數時候 agent 不會理它,原文說 94% 的情況都忽略了,只有 6% 接著做了跟那個 bug 編號有關的動作,例如去查 bug、搜尋相關程式碼或直接改 code。這裡要注意:原文沒寫清楚 6% 的分母是「所有碰到的次數」還是「有碰到的 session」,引用時別講成「6% 的 session」。
第 3 步:自作主張。 在這些後續動作裡,83% 是使用者沒有要求的(原文說「over 4 in 5」)。開發者的 prompt 根本沒提到那個 bug,是 agent 自己決定去追。
第 4 步:追錯東西,或做太多。 這些自發的支線可以分兩類:
- 41% 追的是已經關掉的票:32% 早就修好並驗證過,9% 被標成過時或不修,另外還有 2% 是重複或權限受限的票。也就是 agent 花資源去處理一個早就不存在的問題。
- 57% 的 bug 還開著(NEW/ASSIGNED/ACCEPTED)。這類裡 61% 只是查一查,39% 直接改了 code。bug 雖然是真的,但不在這次任務的範圍內。
原文舉了兩個例子,剛好對應這兩類:
- 「The Obsolete Detour」:agent 在做一件無關的調查時打開
SurfaceControlTest.java,看到一行// TODO(bug/XXX)。那個 bug 2023 年就修好了,agent 還是花了好幾步去查。 - 「The Eager Agent」:agent 在處理一個 bug 時看到另一則 TODO,沒人要求就去改
ModelDownloaderV1的程式碼,中間還做了好幾次程式碼和知識搜尋。後來人類質疑它擴大範圍,agent 也承認改這段不對。
原文說這兩個例子本身都不嚴重,但都浪費了時間和 token,還要人出手把 agent 拉回來。
說到底,agent 讀到 TODO 時,沒辦法分辨這是「現在該做的事」還是「以前留下的紀錄」。註解不會標日期,也不會標狀態。人有背景知識可以判斷,agent 只看得到字面意思。
四、數據與限制:成本有多大、能信到什麼程度

成本
研究方用多重固定效果迴歸,比較「有自發追 bug」和「沒有」的 session,控制了開發者、codebase 複雜度、任務類型、模型等級、讀了幾個檔、初始 prompt 長度,以及 bug 註解在第幾步出現。標準誤以開發者為群集。結果(皆 p<0.0001):
| 指標 | 有自發追 bug 的 session |
|---|---|
| 步數 | 多 70% |
| token | 多 77% |
| 等待時間 | 多 105% |
等待時間多一倍是最痛的。你以為 agent 在處理你交代的事,其實它有一半時間在處理別的事。
清理的效果
Google 做了一個「todo cleaner」,過去 6 個月清掉了「thousands of actually obsolete references」,人工審查的接受率超過 95%。他們的做法是先確認 TODO 的用意已經過時,確認了才送 code review。
限制(引用前請先看)
- 這是觀察性資料,不是對照實驗。 迴歸控制了很多變數,但「有追 bug 的 session 本來就比較複雜」這種混淆,還是沒辦法完全排除。+70%/+77%/+105% 比較適合當作量級的參考,不要當成精確的因果效果。
- 環境是 Google 內部。 原文沒說用了哪些 agent 和模型,只提到控制了「model tier」。換到你的 repo 和 agent,比例可能差很多。
- 只量了一種干擾。 只有帶 bug 編號的 TODO/FIXME。沒編號的 TODO、過時的文件、舊的設定範例都不在範圍內。
- 6% 的分母不明確,前面講過了。
- 原文沒有附論文、程式碼或資料集,只有這篇部落格。目前沒辦法自己重現。
五、手把手:三層防線
下面的做法分成三層:把現有的過期 TODO 清掉、擋住新的進來、在 agent 這邊設圍欄。前兩層是 Google 原文提的方向(presubmit 分析器、全庫掃描、用 agent 清理),我把它改寫成一般團隊能用的版本。第三層是我們的建議,原文沒有測過。
第 1 層:盤點 repo 裡的過期 TODO
Step 1:先看規模。
# 列出所有帶 issue 編號的 TODO/FIXME(以 GitHub 的 #123 格式為例)
git grep -nE '(TODO|FIXME)\(#[0-9]+\)' | wc -l
# 沒有編號、無從查證的 TODO 也順便數一下
git grep -nE '(TODO|FIXME)([^(]|$)' | wc -l
你們的格式可能是 TODO(JIRA-123)、FIXME(gh-123) 或 TODO(bug/123),regex 照著改就好。
Step 2:比對 issue 狀態,找出指向已關閉票的 TODO。
用 GitHub 的話,gh CLI 就夠了:
#!/usr/bin/env bash
# stale-todos.sh:列出指向「非 OPEN」issue 的 TODO/FIXME
git grep -nE '(TODO|FIXME)\(#[0-9]+\)' | while IFS= read -r line; do
num=$(printf '%s' "$line" | grep -oE '\(#[0-9]+\)' | head -1 | tr -dc '0-9')
state=$(gh issue view "$num" --json state -q .state 2>/dev/null || echo UNKNOWN)
[ "$state" != "OPEN" ] && echo "[$state] $line"
done
輸出會像 [CLOSED] src/auth/session.ts:88: // TODO(#412): retry on 503。這份清單就是你要處理的過期 TODO。
Step 3:讓 coding agent 幫你清,但驗證步驟要寫死。
Google 特別提醒,刪 TODO 前要先確認它真的過時了,除了看 bug 狀態,還要看檔案和 bug 的歷史,這些歷史可能長達好幾年。issue 關掉不代表註解講的事情做完了,有時候票是被合併到別的票,或修了一半就關了。
下面這段 prompt 可以直接丟給 Claude Code、Codex 或 Cursor 的 agent 模式:
任務:清理過期的 TODO/FIXME 註解。只處理 stale-todos.txt 裡列出的項目。
對每一則:
1. 用 `gh issue view <num>` 讀 issue 的狀態、關閉原因、最後幾則留言。
2. 用 `git log -S "<TODO 內文關鍵字>" -- <檔案>` 和 `git blame` 看這行是什麼時候、為什麼加的。
3. 讀 TODO 周圍的程式碼,判斷註解描述的事情是否已經被處理。
4. 分類:
- OBSOLETE:issue 已修好且程式碼已反映 → 刪除註解
- STILL_RELEVANT:issue 關了但事情沒做完 → 不刪,列進報告並建議重開或開新票
- UNSURE:證據不足 → 不動,列進報告
5. 不要修改任何註解以外的程式碼。不要順手修 bug。
最後輸出一張表:檔案:行號 / issue / 分類 / 一句話理由。
每 20 則開一個獨立的 PR,方便人工 review。
重點有三個:只處理清單上的項目、分類時要附證據、碰到 UNSURE 就不動。這跟 Google 的 todo cleaner 一樣,確認過時才送 review。最後一定要有人看過再合併。
第 2 層:在 CI 擋住新的過期 TODO
清完一次,半年後又會長回來。Google 原文提到要用 presubmit 分析器和全庫掃描持續處理。小團隊的做法可以這樣:
做法 A:規定 TODO 一定要帶編號(用 pre-commit 或 CI 檢查)。
# ci/check-todo-format.sh:這次 diff 新增的 TODO 必須帶 issue 編號
if git diff origin/main...HEAD -U0 | grep -E '^\+' | grep -E '(TODO|FIXME)' \
| grep -vE '(TODO|FIXME)\(#[0-9]+\)'; then
echo "新增的 TODO/FIXME 必須帶 issue 編號,例如 TODO(#123)"
exit 1
fi
沒有編號的 TODO 之後沒辦法查證,也就沒辦法自動清。
做法 B:排程跑 stale-todos.sh,每週一次,有結果就自動開 issue 或丟到團隊頻道。可以掛在 GitHub Actions 的 schedule。
做法 C:關 issue 時順便檢查。 在 PR 模板加一行:「這個 PR 關掉的 issue,在 code 裡還有 TODO(#n) 嗎?」用 git grep "TODO(#n)" 查一下就知道。
第 3 層:在 agent 這邊設範圍圍欄(我們的建議,待驗證)
前兩層是處理 repo 本身。第三層是告訴 agent 讀到 TODO 時該怎麼做。Google 原文沒有測過這招,以下是根據它的機制推出來的建議,值得在你的 repo 實測。
寫進 AGENTS.md/CLAUDE.md/.cursor/rules:
## 範圍規則:TODO 與 FIXME
- 程式碼裡的 TODO、FIXME、HACK 註解是「歷史紀錄」,不是給你的任務。
- 除非使用者的指令明確提到某個 issue 編號,否則不要去查、去改、去修那個 issue。
- 如果你認為某則 TODO 跟目前的任務真的有關,不要動手,在最後的回報裡列出:
「發現可能相關:<檔案:行號> TODO(#n),原因:<一句話>」,由使用者決定。
- 不要因為看到 TODO 就擴大這次修改的範圍。
這條規則的目的不是讓 agent 什麼都不看,而是把「動手」改成「回報」。57% 的支線追的是還開著的 bug,有些可能真的有用,交給人判斷就好。
單次任務的 prompt 也加一道範圍限制,在任務比較大或 repo 比較舊的時候特別有用:
只改跟「登入頁按鈕對齊」有關的檔案。
過程中看到任何跟本任務無關的 TODO/bug,一律只記下來、不處理,最後一併列給我。
做驗證比較好的方式:挑 5 到 10 個過去 agent 跑偏過的任務,加規則前後各跑一次,比較步數、token 和有沒有跑出範圍。Claude Code 的 /cost 或各工具的 session log 都看得到這些數字。
延伸:其他會讓 agent 分心的東西(推論,無數據)
Google 只量了 TODO,但同樣的機制套到下面這些東西應該也成立,就是「人會略過、agent 會當真」:
- 過時的設計文件和 ADR:例如
docs/裡一份三年前的遷移計畫,agent 可能照著做。 - 死碼和舊版模組:像
legacy/、v1/這種目錄,agent 找參考寫法時可能抄錯版本。 - 封存的對話紀錄、舊的 spec。
能刪的就刪。不能刪的,至少不要讓 agent 讀到:
- Claude Code:在
.claude/settings.json用 permissions 擋讀取,例如"permissions": { "deny": ["Read(./docs/archive/**)", "Read(./legacy/**)"] }。 - Cursor:放
.cursorignore。 - Aider:放
.aiderignore。 - 或在 AGENTS.md 寫明:「
docs/archive/是封存資料,不代表目前的設計,除非我要求,不要讀。」
六、什麼時候值得做、什麼時候別急
值得優先做的情況:
- repo 歷史長、TODO 很多。先跑 Step 1 看數量,上百則就值得處理。
- 團隊大量用 agent 做跨檔案的任務(重構、找 bug、加功能)。agent 讀的檔越多,碰到 TODO 的機會越大。
- 你常看到 agent「做了你沒叫它做的事」。
可以晚點做的情況:
- 新專案或小 repo,TODO 不多。
- agent 主要只做單檔、範圍很明確的小任務。
要注意的取捨:
- 不要為了讓 agent 好讀就把 TODO 全部刪掉。 有些 TODO 記的是真的技術債,刪了資訊就沒了。原則是改成帶編號、能查證,而不是消滅 TODO。
- 範圍規則寫太死,agent 會變笨。 有時候 TODO 剛好就是你這次問題的根因。所以規則寫「回報」而不是「忽略」。
- 自動清理一定要有人審。 Google 的接受率 95% 以上,是建立在先驗證才送審的前提上。直接讓 agent 批次刪除,難保不會刪到還有用的。
七、對工程團隊的意義:一份可以帶走的檢查清單
這份研究讓我們知道,repo 的維護狀況會直接影響 agent 的成本和準確度。以前清 TODO 是「有空再做」的雜事,原文也建議要重新評估這些被延後的清理工作,因為 agent 大量跑起來之後,這些舊註解每天都在浪費資源。
這週就能做的事:
- 跑
git grep數一下 repo 裡有多少帶編號和不帶編號的 TODO。 - 用
stale-todos.sh找出指向已關閉 issue 的那些。 - 用上面的 prompt 讓 agent 分類,人工審完再合併。
- 在 CI 加上「新 TODO 必須帶編號」的檢查。
- 在 AGENTS.md 加上 TODO 範圍規則,挑幾個舊任務比較前後的步數和 token。
- 用 deny 規則或 ignore 檔,把封存資料夾從 agent 的讀取範圍拿掉。
下次 agent 跑偏的時候,除了改 prompt,也翻一下它的讀檔紀錄,看是哪一行讓它分心的。
來源
- Sam Saccone、Madhuri Garikapati(Google),〈Agent Distraction〉,Google Developers Blog,2026-11-06:https://developers.googleblog.com/agent-distraction/ (本文所有數字皆來自此文;原文未附論文、程式碼或資料集,未說明使用的 agent/模型。)
- 文中
gh、git grep、Claude Code permissions、.cursorignore、.aiderignore為各工具的既有功能;第 3 層的 AGENTS.md 範圍規則為 DataAgent 的建議,原研究未驗證。
整理:DataAgent · Coding Agent 實戰教學
文章裡這些把關做法,要在一個團隊裡真的落地、而不是只有你一個人在用,通常卡在流程與共識。我把這部分整理成企業內訓: