本日核心價值 (Core Focus): 只貼 stack trace、故障點前後約 30 行、以及 last request id,讓 Codex 把每一幀對到檔案與假說,再給最小重現,而不是吞掉整份 log。
概念說明與實戰情境 (Overview)
線上故障時,把完整 log 丟進模型通常會稀釋訊號:輪詢、健康檢查、重試噪音會把真正的 KeyError / NullReferenceException 蓋掉。有效的 Debug 工作流是裁切輸入:例外類型與訊息、由上而下的 stack frames、每個相關檔案在故障點附近約 30 行、以及能串起單筆請求的 request_id(或 trace id)。然後要求結構化報告:frame → file → hypothesis → smallest repro。Codex 負責對齊符號與提出可執行的下一步;開發者負責用最小重現驗證,而不是讓模型直接在生產環境「猜一刀」。
關鍵操作與範例 (Implementation & Example)
輸入包三件就夠,刻意不貼整檔 log:
req-7f3c2a,以及該請求的 method / path / 關鍵參數名(不要貼 token)Prompt 把對齊規則寫死,避免模型跳過中間幀直接怪「網路不穩」:
You are debugging with trimmed evidence only.
Given:
- exception + stack trace
- ~30 lines of source around each application frame
- last request_id (use it as the correlation key)
Produce JSON with this shape:
{
"request_id": "",
"exception_type": "",
"frames": [
{
"order": 1,
"file": "",
"symbol": "",
"line": 0,
"what_this_frame_does": "",
"hypothesis": "",
"confidence": "high|medium|low"
}
],
"most_likely_root": "",
"smallest_repro": {
"type": "unit|script",
"steps": [],
"input": {}
},
"do_not_touch": ["unrelated refactors", "production data deletes"]
}
Rules:
- Map every application frame to a file before proposing a fix.
- Prefer the first application frame that throws, not the web framework internals.
- smallest_repro must be the shortest command or pytest that should raise the same exception.
- If evidence is missing, say what file/line to open next; do not invent locals.
- Do not dump or request full logs.
Python 常見的 KeyError(對齊 Day 11 訂單欄位):請求 body 缺 coupon_percent 時不該炸,但若程式用 payload["user"]["id"] 而 gateway 只給 user_id,就會在應用層拋錯。
request_id=req-7f3c2a POST /orders
Traceback (most recent call last):
File "app/http.py", line 41, in handle
return create_order(payload)
File "app/orders.py", line 18, in create_order
user_id = payload["user"]["id"]
KeyError: 'user'
# app/orders.py(故障點前後約 30 行,已裁切)
def create_order(payload: dict) -> dict:
# 預期巢狀 user.id;實際 gateway 送來扁平 user_id
user_id = payload["user"]["id"]
unit_price = Decimal(str(payload["unit_price"]))
quantity = int(payload["quantity"])
tax_rate = Decimal(str(payload.get("tax_rate", "0")))
coupon = payload.get("coupon_percent")
coupon_percent = None if coupon is None else Decimal(str(coupon))
total = calculate_line_total(unit_price, quantity, tax_rate, coupon_percent)
return {"user_id": user_id, "total": str(total)}
C# 對等的 NullReferenceException 同樣只給幀與附近程式,不貼 IIS / Kestrel 全文:
request_id=req-7f3c2a
System.NullReferenceException: Object reference not set to an instance of an object.
at Orders.OrderService.Create(OrderRequest req) in OrderService.cs:line 22
at Orders.OrdersController.Post(OrderRequest req) in OrdersController.cs:line 31
public OrderDto Create(OrderRequest req)
{
var userId = req.User.Id; // req.User 為 null(客戶端只送 UserId)
var total = _pricing.Calculate(req.UnitPrice, req.Quantity, req.TaxRate, req.CouponPercent);
return new OrderDto(userId, total);
}
模型應產出可機器解析的報告(欄位可再給 JSON Schema,接 Day 03 的結構化輸出):
{
"request_id": "req-7f3c2a",
"exception_type": "KeyError",
"frames": [
{
"order": 1,
"file": "app/orders.py",
"symbol": "create_order",
"line": 18,
"what_this_frame_does": "從 payload 讀巢狀 user.id",
"hypothesis": "此請求沒有 user 鍵,只有扁平欄位;KeyError 發生在計算金額之前",
"confidence": "high"
},
{
"order": 2,
"file": "app/http.py",
"symbol": "handle",
"line": 41,
"what_this_frame_does": "把 HTTP JSON 轉給 create_order",
"hypothesis": "框架不是根因;它只是轉傳 payload",
"confidence": "high"
}
],
"most_likely_root": "契約不一致:gateway 送 user_id,服務讀 payload['user']['id']",
"smallest_repro": {
"type": "unit",
"steps": [
"pytest -q tests/test_create_order.py::test_flat_user_id_payload"
],
"input": {
"user_id": "u-1",
"unit_price": "19.99",
"quantity": 1
}
},
"do_not_touch": ["pricing.py rounding", "database schema"]
}
有了 smallest_repro 再請 Codex 補測試或修讀取邏輯。寫檔時同樣使用:
codex exec --sandbox workspace-write \
"Implement the smallest fix for KeyError in create_order using artifacts/debug-req-7f3c2a.json. Add one unit test with the flat payload. Do not refactor pricing."
Debug 報告可放 artifacts/debug-<request_id>.json,之後 Day 15 的 Tester / Reviewer 都讀同一份,而不是重貼 log。若必須再補證據,只加「同一 request_id 的下一筆應用日誌」,仍然不要整檔。同一 request_id 若同時出現超時與 KeyError,先處理有明確應用幀的那筆;超時通常缺少檔案行號,不適合當第一次對齊練習。
對齊順序建議固定:先標第一個屬於你們 repo 的拋錯幀,再看它讀了哪個欄位,最後才考慮下游(DB、SDK)。框架幀(uvicorn、ASP.NET)留下當上下文即可,不要當成根因,除非 hypothesis 明確指向 middleware。
「約 30 行」的取法:以拋錯行為中心,向上約 15 行、向下約 15 行,涵蓋該函式的輸入解構即可。不要為了湊行數貼整個 controller。若 Release 建置沒有行號,改貼符號名稱加上「該方法完整本體」(仍控制在一個函式內),並在 JSON 的 confidence 標 low,下一步改為開啟對應的 debug 建置或 PDB。
request_id 應在第一行結構化日誌就出現,例如 {"request_id":"req-7f3c2a","event":"exception","type":"KeyError"}。模型用它當關聯鍵,才能拒絕把另一筆超時例外寫進同一份報告。JSON 報告本身也要入 artifacts/,後續補測試時引用檔案,而不是再貼一次 stack。最小重現若是單元測試,斷言應是「同一 payload 仍引發同一例外類型」,修過後同一測試改為斷言回傳 user_id,不要另開一個無關案例。
C# 的 NullReferenceException 對齊方式相同:先看 OrderService.Create 第 22 行讀了 req.User.Id,再對照請求 JSON 是否只有 userId。假說寫「客戶端契約是扁平欄位、服務模型假設巢狀物件」,最小重現是一個只送 UserId 的單元測試。修法是讀扁平欄位或在反序列化時要求 User 必填並回 400,而不是在 catch 裡吞掉 null。兩種修法哪一種正確,要看 API 契約;這也是為什麼 Day 14 要把欄位寫進 OpenAPI,而不是只靠例外訊息。對齊完成前不要改 pricing.py。
注意事項與常見失敗 (Pitfalls)
request_id 的 trace + 30 行。KeyError 與 NullReferenceException 的修法完全不同。修法:保留完整 stack。smallest_repro,綠燈前不合併。do_not_touch 列出定錨檔案。low,並列出下一步要打開的檔案,而不是直接改程式。本日總結 (Takeaways)
KeyError / NullReferenceException 多半是契約(巢狀 vs 扁平、null vs 缺欄),先對齊讀取點。codex exec --sandbox workspace-write 做最小修復。明日預告 (Next)
明天進入 API 文件自動化:從 Swagger/OpenAPI 一鍵生成 Markdown 規格書,把「payload 到底有沒有 user 巢狀」寫成可檢查的契約,而不是只存在例外堆疊裡。