終端機一直在刷:grep、開檔、跑 test、換 query、再開另一批檔案。這種 Codex session 很容易讓人誤判,因為畫面看起來有進度。十分鐘後把問題重新問一次,能排除的原因可能一個都沒變。

Codex 的 agent loop 會放大這個成本。OpenAI 對 agent loop 的拆解裡,一個 turn 可以在 model inference 和 tool call 之間跑很多輪;到了下一個 turn,先前的 messages 與 tool activity 也會跟著 conversation history 進入 prompt。沒有換來新 evidence 的探索,代價不只在執行時間。每一輪 inference 都會產生 token,而累積的 tool output 還會讓後面的 context 更重。[1]

這篇只處理兩種很容易把長 debugging 拖慢的狀況。第一種叫 wandering:Agent 一直探索,卻沒有讓可能原因變少。第二種是 whack-a-mole:哪裡紅就先修哪裡,局部 case 一個個變綠,上游問題是否存在、在哪一層,反而一直沒有被證明。

判斷 session 有沒有前進,可以先抓兩個問題:上一個動作多帶來了什麼新資訊?沿著這次實際走過的 path 往前看,expected state 和 actual state 最早在哪裡分開?

忙了十分鐘,能排除什麼?

假設 bug 橫跨幾個 package。Codex 先搜一個 symbol,讀到其中一個 match,沿著 import 追到另一個 package,再看到 helper、interface、test fixture。每一步單獨看都合理,問題出在整條 trajectory:探索樹一直長,能成立的原因卻沒少多少。

Sourcegraph 在 CodeScaleBench 記錄過一個很直觀的例子。資料來自 1,281 次 scored agent runs,涵蓋 40 多個大型 open-source repositories。其中一個 Kubernetes 任務用一般 local file exploration 跑到 6,000 秒仍沒有產出;同一個 agent、同一個 task 改用 indexed keyword search、semantic search 和 find-references 後,89 秒完成。[2]

89 秒不是拿來推算你的 repo 會快幾倍。Sourcegraph 本身就是相關 context tooling 的供應商,這組結果也不是 Codex benchmark。這個案例能說明的是 wandering 的形狀:一連串 locally sensible 的動作,合起來仍然可能沒有收斂。

OpenAI 在 Harness Engineering 裡記錄的是另一種卡法。Codex 一旦做不下去,團隊會回頭補 agent 缺少的 tools、structure、guardrails 或 feedback loops,而不是只把同一個任務再丟回去。UI state、logs、metrics、traces 能不能被 Codex 直接查到,會影響下一步是在猜,還是在根據系統狀態做判斷。[3]

所以 debugging progress 可以用一個比較狠的問題來看:

這一步之後,哪個 hypothesis 已經被目前 evidence 排除了?

如果答案是「還沒有」,這一步可以算 exploration,但 diagnosis 還沒往前走。

Google SRE 的 troubleshooting 方法本來就在做類似的事:從 observation 和 system model 形成可能原因,再用 confirming 或 disconfirming evidence 測試它們。好的 experiment 會讓不同結果指向不同解釋。[4]

幾種很容易耗掉一輪、卻沒有帶來多少資訊增益的動作,也就比較好辨認了:

  • 同一批檔案換幾種 query 重搜,但 search strategy 和 hypothesis 都沒變。
  • 原封不動重跑同一個 failing test,又沒有在查 nondeterminism。
  • 多讀一個相關 module,卻說不出它能支持或排除哪個原因。
  • 上一個 bug 曾經出在 parser,這次就先照 parser 的方向走。

最後一種尤其常見。過去發生過的原因可以當 prior,不能直接當這次的 diagnosis。

Evidence Ladder:把 investigation 壓回五格

長 session 開始散掉時,可以先把目前知道的東西壓回五個欄位。本文把這張工作表稱為 Evidence Ladder。它不是 Codex 內建功能,也不是 OpenAI 的官方術語。

欄位要留下什麼危險訊號
Problem statement可觀察的 mismatch:哪個 input、output、test 或 invariant 不符合預期一開始就把猜測寫成 root cause
Current evidence已經看到的 trace、test result、code path、log、repro、diff推測比 observation 多
Hypotheses ruled out by current evidence目前證據已經排除什麼,以及排除依據只寫「應該不是」
Remaining hypotheses還能解釋現象的少數原因每讀一個檔案就多長一個故事
Next discriminating test結果可以把剩餘 hypothesis 分開的檢查再做一次沒有變化的檢查

例如 final output 少了一個欄位,目前有兩種說法:retrieval 根本沒拿到資料,或 retrieval 有拿到,但 mapping 把欄位掉了。這時候再泛搜 repo 幫助不大。直接看 retrieval output 進入 mapping 前的 seam,比多讀幾個 module 更有判別力。

如果欄位在 retrieval output 已經存在,retrieval 這條 hypothesis 至少在這個 repro 上可以排除;如果資料根本沒進來,mapping 還沒有證據值得動。

這個 discriminating test 不必做成完整 unit test。focused trace、fixture、breakpoint、單一路徑 command,甚至一個能直接看到中間 state 的 query 都可以。測完之後,對「問題可能在哪」的判斷有改變,它就有價值。

Agent Wandering vs Evidence-Driven Progress:追蹤 information gain,而不是 tool activity

Figure 1|Wandering loop 看起來很忙,但沒有 new evidence;右側則用 discriminating test 讓 hypothesis space 變小。

綠燈變多,也可能只是補丁變多

Whack-a-mole 比 wandering 更容易騙過人,因為每一輪真的會有東西被「修好」。

先用一條示意 pipeline:

Input

Retrieval

Normalisation

Mapping

Verification

Output

Output A 錯了,在 formatter 加一個條件,A 綠。接著 B 又錯,再補一條。第三個 case 出現後,grader 也開始被調整。repo 每一輪都有 diff,case-specific logic 越長越茂盛,但大家還沒回答:這幾個 failure 是三個獨立問題,還是有一個更早的 mismatch 往下傳?

OpenAI 的 Tax AI 把這條路徑做得很完整。產品不只保留 final input/output,而是保留從 source material、extracted fields、provenance 到 downstream submission 和 practitioner correction 的 path。重複出現的 field-level correction 會先被 review、分組,再變成 targeted eval。Codex 接手 investigation 時,可以一起看 source package、extraction schema、mapper、grader、repo 和 eval,區分 unsupported field、extraction miss、source-selection problem、mapping gap,或只是 grader 把正常 workflow noise 算成 failure。[5]

有了這種 trace,debugging 才能從 final output 往上游走,不必一直盯著最後那個錯誤值。

找第一個偏掉的 seam

本文把這個位置叫 Earliest Divergence

在一條可以觀察的處理路徑上,actual state 第一次和 expected state 出現有意義差異的位置。

它不是 Codex 官方 feature。它標的是目前這條可觀察 path 上最早的 mismatch,不保證那一層就是最後的 root cause。

這個方法也有邊界。它最容易用在一條具體 trace 或 causal chain 上;遇到 async fan-out、race condition、distributed workflow,不能先假設整個系統存在唯一的 global first divergence。比較實際的做法是沿著各自可觀察的 trace 找第一個 mismatch,再看這些 mismatch 是否真的收斂到同一個原因。

Expected path 可能長這樣:

Source contains field
→ Retrieval returns field
→ Normalisation preserves field
→ Mapping emits field
→ Verification accepts field
→ Output is correct

實際看到的卻是:

Source contains field
→ Retrieval returns field
→ Normalisation preserves field
→ Mapping drops field   ← first divergence
→ Verification fails
→ Output is wrong

Verification 和 Output 都紅了,但在這個例子裡,它們是更早的 mismatch 傳下來之後才壞。直接去改 formatter 或 verifier,可能讓眼前的 case 消失,卻沒有碰到造成它的 mechanism。

Google SRE 把 symptom 的「what is broken」和 cause 的「why」分開。[6] Delta Debugging 會縮小 failure-inducing input,或隔離 passing / failing cases 之間與 failure 有關的差異。[7] Google 後來也把 automated bisection 用在 fuzzing bug localisation,找 crash 第一次出現的 commit;他們報告的經驗裡,附帶這類 localisation 資訊的 bug,平均修復速度是原本的 2.23 倍。[8]

對 Codex 沒必要把每個問題都做成自動 bisection。先把 path 畫出來,逐個看重要 seam 的 input/output 就夠用了。在這類串接式 pipeline 裡,設計下一個 test 時先看第一個不再符合 contract 的位置,比盯著最後一個紅燈更有用。

Surface Patching vs Earliest Divergence:先定位第一個 observable mismatch,再決定 patch layer

Figure 2|Surface patching 會讓 case logic 一直長;Earliest Divergence 先找 expected 和 actual state 最早分開的位置,再把修正放到 evidence 支持的 layer。

Earliest Divergence 也不等於「禁止 surface patch」。如果 parser 就是第一個偏掉的 seam,parser patch 當然合理;上游 state 全部正確,formatter 卻違反明確 output contract,那就修 formatter。

Case-specific logic 也是一樣。有些 domain rule 本來就只有特殊 case。要查的是那條 branch 到底在表達既有的 business contract,還是在替更早壞掉的 mechanism 擦屁股。

Patch 前可以先過四題:

  1. proposed patch 是否落在目前觀察到的第一個 divergence?
  2. 它能解釋同一群 related failures,還是只讓一個 symptom 消失?
  3. 有沒有 focused check 能在修改前失敗、修改後通過?
  4. implementation 真的被修了,還是 grader、expected output、threshold 一起被放寬?

如果 grader、expected output 或 threshold 跟著 patch 一起變寬,綠燈就不能證明 implementation 真的被修好。

Session 已經散掉了,先別改 code

如果 Codex 已經讀了一大圈、failures 開始成串、每個 local fix 又冒出下一個 case,先別再叫它「仔細找 root cause」。把 investigation 壓回可以檢查的狀態,再決定下一刀放哪裡:

Before changing code, report:

1. The current problem statement.
2. New evidence obtained since the previous step.
3. Which hypothesis that evidence supports or rules out.
4. The earliest observable layer where expected and actual behaviour diverge.
5. The smallest test that can distinguish the remaining hypotheses.

If there is no new evidence, do not repeat the same search or rerun unchanged checks.
Do not present a downstream symptom patch as the root-cause fix unless the evidence supports that layer as the cause. A downstream containment, mitigation, guardrail, or explicit domain rule may still be justified; label it as such and keep the upstream diagnosis open.

這段不是萬用 system prompt。repro 很乾淨、原因已經明確的小 bug,直接修比較快;硬套一層 investigation ceremony 只是在增加成本。

它比較適合那些已經失去方向的 session:讀了很多檔案、failures 同時出現、local fixes 開始累積,卻說不清楚上一輪到底學到了什麼。這時候把 problem statement、evidence、remaining hypotheses 和下一個 discriminating test 固定下來,至少能看見 investigation 有沒有真的變窄。

長 debugging 不需要每一輪都找到 root cause。下一步只要能讓一個解釋留下、另一個解釋被目前 evidence 排除,就比再跑一輪沒有判別力的探索有用。幾個 failure 同時冒出來時,也先別急著逐個消紅燈;把 expected path 和 actual path 並排,從第一個有意義的 divergence 開始查。

下一個人接手時,最好留下兩件事:哪些路已經不用再走,以及下一個 test 能把什麼分開。

參考資料

  1. OpenAI, Unrolling the Codex agent loop, 2026-01-23.
  2. Sourcegraph, Why coding agents fail in large codebases (and what to do about it), 2026-05-08.
  3. OpenAI, Harness engineering: leveraging Codex in an agent-first world, 2026-02-11.
  4. Google SRE, Effective Troubleshooting.
  5. OpenAI, Building self-improving tax agents with Codex, 2026-05-27.
  6. Google SRE, Monitoring Distributed Systems.
  7. Andreas Zeller and Ralf Hildebrandt, Simplifying and Isolating Failure-Inducing Input, IEEE Transactions on Software Engineering 28(2), February 2002.
  8. Google Research, Reducing Time-To-Fix For Fuzzer Bugs, 2021.