CLAUDE.md 過期時,用一行 prompt 抓出腐壞

專案裡的 CLAUDE.md 寫完幾個月忘了改,其實是很常見的事,我原本以為現在 AI 這麼聰明,看到說明書跟現場程式碼對不上至少會停下來問一句。結果讓 agent 拿 10 個專案實際跑一輪才發現,有 7 個(70%)直接照著過期說明書把風格寫反,照樣在我原本全域規則規定交差前必附的自檢核對項目裡工整填上無漏做、結果吻合,而且其中 6 個從頭到尾連衝突都沒吭聲 🤣

後來看到這篇論文 Stale Instruction Files Override the Code in Coding Agents: A One-Sentence Fix,我才搞懂這其實是模型訓練時的指令階層設計惹的禍。

模型被訓練成先信系統說明書,不信眼前的程式碼

說穿了,這件事跟模型偷懶沒太大關係,根源出在防範惡意 prompt 注入的對齊訓練。

模型在訓練時會被教成「系統層級的指令永遠壓過工具讀回來的外部內容」,免得讀個檔案就被裡面藏的字句帶偏。問題在於,不管是 CLAUDE.md 還是 AGENTS.md,都是以系統指令的身分直接塞進 context 的,而專案裡真正活著的程式碼,反而是 agent 靠讀檔工具一點一點撈回來的輸出。

兩邊一旦對不上,AI 就會像死背泛黃員工手冊的菜鳥,哪怕眼前整條產線早就換成新型號了,它還是把那份過期說明書當聖旨供起來,照著舊規則硬寫,有些情況甚至會反過來改動原本正常的程式碼,把測試改到壞掉。

論文團隊把公開基準放進 WenJing95/stale-docs-data 測了 Claude Code 跟 Codex CLI,沒加任何提示前,整體高達 90.1% 的比例都順著過時文件寫;等到在使用者層級的全域規則檔加上一句英文規則之後,情況直接大翻轉,反過來有 91.4% 改回照現有程式碼寫。

翻對話紀錄抓不到,實測十個專案七成照錯寫還回報沒問題

知道這個機制之後,我本來想先讓 Claude Code 去翻我自己的歷史 session 紀錄,看看平常到底踩過多少次這種坑。結果撈完才發現這招沒用,翻舊對話只能看到 agent 自己主動開口講「這裡有衝突」的少數紀錄,那些沒開口就照錯寫的案例在紀錄裡根本隱形。

於是我請 Claude Code(以下簡稱 CC)從論文那個公開基準挑出 10 個專案,把平常用的 hook、skill 跟個人記憶先隔離掉,單純載入我這套全域規則文字跑實測,看它在錯誤處理或命名這類慣例衝突時到底聽誰的。

沒加規則前跑出來的結果蠻讓人無力的:

接著我讓 CC 在全域 CLAUDE.md 的第 1 行,擺進論文實測有效的那一句英文原文,故意放在最開頭就是為了讓模型一讀進系統規則就先立好優先順序,才不會被後面幾百行其他設定淹沒:

“When a repository’s documentation and its existing code disagree, follow the conventions used in the existing code, and in your final reply point out each inconsistency you found between the documentation and the code.”

這裡刻意不翻成中文,原因很單純,只有這句英文原文經過基準實驗驗證,直接沿用比較穩,省得自己亂翻譯又搞出變數。我也順手把同一句英文放進日常當副線用的 Codex CLI 全域 AGENTS.md 開頭,自動收尾記帳則先接在主力 Claude Code 工作流上。

加上第 1 行之後再讓 CC 重測同一批專案,照過時文件寫的比例直接掉到 0%(10/10 個專案全數改照現有程式碼慣例寫,而且每份回覆都在結尾把文件跟程式碼對不上的地方逐項列出來)。不過這 10 個都是小型合成 Python 專案,我沒有另外跑空白全域設定的對照組,而且前後兩組測試都沒有改壞既有功能測試,真正的代價是新寫出來的程式碼風格偏離現場慣例,得靠人回頭重工。當然這句話也有它的觸發前提,任務中必須同時接觸到相關文件說明與對應的原始碼才會觸發比對;如果只看了文件就直接作答、沒去翻程式碼,一樣抓不到落差。

75 份規則檔擺在那裡,靠人巡檢根本不現實

測到這裡,下一個很自然的念頭是:既然病灶是說明檔過期,那抽空把每個專案的 CLAUDE.md 全部改對就好。

我請 CC 盤點了一次個人跟工作目錄下的規則檔,當時掃出 75 份 CLAUDE.md 跟 AGENTS.md,其中有 30 份自從建檔後只 commit 過 1 次就再也沒人動過,有些活躍專案在說明檔最後修改後早就又累積了幾百次程式碼 commit。這 75 份裡混了平行開發切出的工作目錄與暫存資料夾裡的重複檔,但就算把重複副本扣掉,散在各專案裡的還是有好幾十份,靠人逐份對帳依舊不現實,而且久沒改也還是得對照現場程式碼才算真的過期。

這現象也不光發生在我身上,外部的 企業 repo 研究 跟 開源專案研究 查出來都一樣,說明檔寫完停在初版快照、過時好幾週才被撞見本來就是常態。

真要靠人去定期擦乾淨,會撞上幾個繞不開的結構限制:說明檔過期不會自己跳通知,拿幾十份文件逐行對現場程式碼的巡檢成本太高;就算今天擦亮了,下週改個寫法它又落後了;更麻煩的是日常碰的 repo 涵蓋個人專案、團隊共用的公司專案、還有從開源專案分叉出來的 repo,公司跟分叉專案的說明書本來就輪不到我一個人單方面直接改檔。

所以我把全域第 1 行這句話重新定位成文件腐壞的煙霧偵測器。前半句負責當下止血,叫 agent 先照現場程式碼寫、別寫出風格怪胎;後半句負責報警,每次任務結束時在回覆末端把過時清單吐出來,當成後面修訂文件的線索。而且因為那句英文寫的是 repository's documentation(整個 repo 的說明文件),不只系統注入的 CLAUDE.md 會被盯上,連任務途中讀到的 README.md 或開發文件過期,一樣會在結尾跟著亮燈報警。

對話結尾會被洗掉,改把落差記進獨立帳本分三路收

只是光靠對話結尾列出清單,實戰跑幾天就會發現這清單留不住,任務做到一半通常沒空立刻去改說明書,這輪 session 一結束,結尾那幾行字馬上被新的對話淹沒。

我原本也想過把這些清單塞進跨 session 備查用的個人記憶檔,但它只會在日後對話被動載入參考,不會主動跳待辦提醒;知識庫蓋好後怎麼讓它保持鮮度這件事,我之前在《wiki 蓋好之後,讓它繼續活著才是真正的問題》聊過,這裡就不重複。拿它去混進原本記 harness 或 skill 哪裡卡手的摩擦紀錄也一樣搭不上,那邊修的是我自己的工具設定,這裡要動的是目標 repo 的說明檔或開 PR。

最後的解法是接進我日常收工的習慣裡。平常做完一個段落準備收尾時,我都會跑一個自訂的收尾 command 來清點變更、分批 commit 並推送,現在我在裡面多加一步:收尾時自動回頭掃一遍這場 session 主對話裡列出的不一致(包含 subagent 回傳上來的摘要),把當場已經順手修掉的扣掉,剩下的就連同目標檔案路徑,追加寫進本機一份集中的 Markdown 文件落差帳本裡。等到下次開新 session,啟動 hook 會先看帳本裡的待處理筆數,累積超過門檻就跳出提醒,叫我跑審查指令把累積的條目逐筆看過(首行規則、這個收尾登記步驟跟審查骨架,我都已經包進公開的個人 Claude Code 設定框架 GGGODLIN/gggodlin-cc-harness 裡)。

進到審查的時候,每一筆都由我親自拍板,再依目標檔案所在的專案路徑走 3 路分流:

  1. 個人 repo:我自己說了算,確認過期點頭後就讓 agent 直接改檔並 commit。
  2. 公司 repo:團隊共用資產輪不到我單方面直改主幹,先由我確認方向(像我之前踩過《我還沒點頭,AI 就寫進文件當結論》那種坑之後,更不會放著 AI 自己亂替團隊寫定論),再讓 agent 附上查核依據開 PR 給同事審閱。
  3. 上游分叉 repo 裡由原作者維護的文件:標成休眠、不在本地亂改,留待以後真的要向上游送文件 PR 再一併處理。

上線九天抓出 186 筆落差,連部落格自己的門牌都過期

這套迴圈上線 9 天,平常照常寫程式、照常收尾,帳本就已經累積攔下 186 筆「文件與現有程式碼不一致」的警報。

蠻好笑的是,開帳沒多久最先被抓包的竟然就是我這個個人部落格 repo 自己,它的 README.md 裡還留著早已退役的舊部署平台跟早就刪掉的目錄,連 CLAUDE.md 裡寫的規則定義路徑都早就搬家了 🤣

截至目前,這 186 筆警報裡有 151 筆已經拍板結案(多數是確認過期後核准修訂,少數則是看過後確認屬於刻意規範或遷移目標而否決不改),剩下的條目,一部分是審查完歸進上游分叉專案休眠區、暫不動刀的紀錄,另一部分是剛進帳還在排隊等我審的項目。收尾登記只抓得到有跑收尾 command、而且有浮上主對話的回報(要是 subagent 在背景自己消化完、沒回傳進主對話,就不會進這本帳);加上同一個檔案不同段落過時會分列多筆,所以這 151 筆結案也不等於改了 151 份獨立文件。

搬家到一半的專案,照現有程式碼寫反而會退回舊寫法

本來以為照著這套一直收就沒事了,結果在剛把第 1 行規則放進全域設定那天、收尾帳本還沒上線前,我先挑了手邊最常碰的幾個核心專案讓 CC 抽查一輪試水溫(這批單次抽查沒有算進前面 9 天累積的 186 筆裡),當場就撞出一個一定要守住的反例:專案剛好搬家到一半的時候,這套規則會直接踩坑。

實務上很常出現一種過渡狀態:團隊已經先把 CLAUDE.md 更新成新標準,但整個專案裡的既有舊程式碼還沒全面翻新。這時候如果還是叫 agent 無條件照現有程式碼慣例寫,它一翻開旁邊殘留的舊檔就會順手照抄,反而退回舊寫法。

在公司後端服務的清查結果裡就看到這個現場:說明檔明訂改用新的驗證裝飾器,但從整個專案清出來的數量來看,新寫法有 200 處,舊寫法還殘留 39 處。同批清查的資料管線專案跟後端匯入路徑也碰到類似狀況,文件寫單一規範,現有程式碼要嘛舊寫法還占多數,要嘛兩邊並存。這時候單看哪邊行數多根本沒用,光靠比例根本分不出這到底是「說明檔過期忘了改」還是「新標準遷移還沒搬完」。

坦白說,外部論文跟我的本機實測都只量測過「說明檔過期、現有程式碼才正確」的單向情境,至於「說明檔寫了新標準、舊碼還沒清完」的反向退化率,還有加註遷移說明後的遵守率,目前都還沒有跑過合成基準量化驗證。

對應的做法是在兩邊各補一層護欄。帳本審查這邊,遇到疑似新舊並存時不讓它自己選邊站,先請 agent 統計新舊寫法在專案裡的出現次數、標上「方向待確認」,再視專案歸屬交給我或團隊同事拍板。說明檔這邊則規定,凡是還在遷移期的規範,直接在文件裡寫明「新程式用 A,既有程式仍存舊寫法 B 遷移中勿照抄」;這樣一寫,現場留著舊寫法 B 就完全符合文件描述、不再構成「文件與程式碼不一致」,全域第 1 行的覆寫規則自然不會誤觸(就算遷移剛起步、現場還全是舊碼,第 1 行也會先在結尾報警提醒人補上這句遷移標註)。至於縮排、型別這類靜態檢查或型別工具本來就能自動驗證的表層格式,一開始就別寫進純文字說明檔等著腐壞,我在《規則寫下來了,agent 真的有收到嗎》也談過類似的拿捏,能讓工具直接擋掉的事就別寫成文字規則耗在這裡。

最後照例疊個甲,這整套做法目前只在我的工作流裡跑通,也還是得靠人定期打開帳本拍板。只是對同時維護一堆專案又懶得天天人工巡檢的人來說,至少不用再等 AI 寫完一整輪錯的還笑著回報一切正常 😇