寫 Claude Code Skill 的文章滿坑滿谷。Skill 寫壞了怎麼修的文章一篇都找不到。
這件事其實比「怎麼寫」重要得多。Skill 是你把工作流程固化成文字的產物,它第一版一定不對——不是語法錯,是判斷規則不夠:某個步驟該查三個地方它只查了兩個、某個結論不該下全稱它下了、某個 probe 腳本在零命中時你分不出「真的沒有」還是「搜錯了」。
這些失誤有個共同特徵:當下你會手動補救,然後就忘了。 下次同一支 skill 跑起來,同樣的地方再摔一次。
RUNLOG 是為了解決這件事。這篇講它的完整機制,以及我踩出來的五條鐵則——其中三條是關於如何不讓規則膨脹成沒人讀的長文。
核心觀念:RUNLOG 是原料,產物是 Skill 本體被改好
先講最容易搞錯的一點。
RUNLOG 是一個 markdown 檔,放在 skill 目錄旁邊,記錄「這支 skill 每一次沒一次到位的地方」。但它不是日記,也不是給人看的成長紀錄。
⚠️ 產物是
SKILL.md/ probe 腳本 / 雷區文件被改好,不是這個檔變漂亮。
這句話寫在我每一份 RUNLOG 的檔頭。因為誘惑實在太大——把失敗寫得很完整、分類很整齊,會產生一種「我處理過了」的錯覺。但如果 SKILL.md 一個字都沒動,下次跑起來的行為完全一樣。
所以整套機制只有一個成功判準:
消化一筆 = 有一個檔案因此被改。
RUNLOG 長什麼樣
格式刻意壓到一行一筆:
- [<來源單號>] <哪一步出錯> — <漏了什麼/誤判什麼> — <當下怎麼補救> [未消化 | 已消化→改了X]
真實的一筆長這樣(節錄):
Step 7 範本 ── 用 openpyxl 產「給舊版 PHPExcel 吃」的 xlsx 範本 = 靜默全毀:openpyxl 3.1 把字串寫成
t="inlineStr"(且不產sharedStrings.xml),PHPExcel 1.8 的 Excel2007 reader 讀不到 inlineStr,於是範本的表頭 25 欄、整頁「填表說明」、統計表的男/女/小計列全部消失,但 load/save 不報任何錯,sheet 名稱/隱藏狀態/欄寬都正常 → 表象是「範本沒問題」。要到解析產出才發現。修法:範本改用 PHPExcel 自己 load 客戶原檔 → 清資料 → save,讓範本天生就是 PHPExcel 讀得回來的形狀。 → 教訓:凡是要餵給舊版 PHPExcel 的範本檔,一律用 PHPExcel 自產或自己 round-trip 一次並解析驗證;驗收條件是「解析產出時標題列/靜態頁有值」,不是「檔案打得開」。[已消化→pitfalls.md §10 + SKILL.md Step 4 觸發清單 J]
一筆裡有四個要素:在哪一步摔的、根因是什麼、當下怎麼救、最後改了什麼。
前三個是原料,第四個是產物憑證。沒有第四個,這筆就掛著 [未消化]。
目前這支 skill 的 RUNLOG 累積到 79 筆,其中 73 筆已消化、6 筆掛著未消化。那 6 筆不是懶得處理,是故意留著的——原因下面說。
鐵則一:先分群再歸納,別用 n=1 過度擬合
這是整套機制最重要的一條。
你剛摔完一跤,最想做的事是立刻在 SKILL.md 加一條「以後要注意 XXX」。這通常是錯的。
因為單一案例分不出「這是系統性缺陷」還是「這次剛好倒楣」。用 n=1 去改規則,你會得到一份針對歷史特例過度擬合的長文——每條規則都是真的,但合起來沒人讀得完,而且沒有哪一條真的防到下一次。
所以條目分兩型:
| 型別 | 判準 | 處理 |
|---|---|---|
| fix-now | 根因確定、系統性、改一行就根除 | 單筆也立刻消化 |
| 累積型(判斷失誤) | 要靠多個案例才看得出 pattern | ≥2 筆同族才歸納成規則 |
fix-now 的典型例子:「probe 腳本執行超過某個時間會被自動背景化,而我沒去撈結果就當它沒命中」。這是工具的確定行為,改一行文件就根除,沒有累積的必要。
累積型的典型例子:「這次根因判錯了,因為我只看了 A 層沒看 B 層」。這種只發生一次,你不知道是流程缺陷還是那天不夠仔細。
孤例的處理方式是:維持 [未消化],並明講「待更多案例」。這就是那 6 筆的來歷。它們不是待辦,是證據不足的觀察。
鐵則二:最小持久改動——這條防的是 SKILL.md 肥胖
消化一筆的產出應該是:一條 rubric、一條小抄、一個 probe case。不是重寫一段。
為什麼這麼小氣?因為 SKILL.md 有一個很多人沒意識到的成本:
SKILL.md每次請求都整份重送,是唯一「不做事也要付錢」的成本。
我實測過一個數字:某次工作的靜態前置約 45k tokens,而那段工作發了 205 次請求。那份文件被完整重送了 205 次。
所以我在盤點腳本的輸出尾段加了一個觸發器:
--all掃描時會印出各SKILL.md的常駐大小。超過 20KB 的,本輪就多做一群「瘦身」。 這一群不需要 ≥2 筆同族,它是 fix-now:規則一直都在,缺的只是觸發器。
這是我覺得整套機制裡最有價值的設計:把「文件該瘦身了」變成一個由數字自動觸發的工作項,而不是靠人偶爾想起來。
案例細節不進 SKILL.md
第二個防膨脹的規則:規則後面不要掛案例、不要掛單號。
錯誤示範:
- 改共用函式前先確認 repo 裡有幾份副本(issue-042 那次 PHPExcel 有三份,
只改一份導致另外兩份的報表照壞,客戶回報後才發現……)
正確寫法:
- 改共用函式前先 md5 確認 repo 裡有幾份副本。
踩過的形狀:同一份第三方函式庫被複製三份,只改一份等於沒改。
差別有三:
- 規則要能獨立讀懂。掛著
(issue-N)的規則,讀的人得去翻單才知道在講什麼——而未來讀它的多半是 AI,它翻不到。 - 「踩過的形狀」是通則敘述,保留了教訓的形狀但去掉了案例的長度。
- 細節各有其家:失敗全文留 RUNLOG、領域事實進雷區文件、指令雷進記憶。複述別處已有的內容 = 同一份真相三個版本,遲早不同步。
鐵則三:搬走不等於刪掉
SKILL.md 瘦身時最容易犯的錯,是把一整段判準搬去附屬文件,然後在原處留一行:
- 詳見 templates.md 的「Step 4B」一節
這等於實質刪除。 因為下次執行時,AI 讀到這行有兩種反應:跳過,或是把整份 templates.md 讀進來——後者比不搬還糟,你省下的 token 一次還回去。
正確的作法是:留下的那一行必須是祈使句的觸發條件 + 可以直接跑的抽節指令:
- 命中「跨模組共用元件」→ `python section.py templates.md "Step 4B"`
這行有三個性質:
- 祈使句:明確告訴執行者「什麼情況下要做什麼」,而不是「有個東西在那裡」
- 可執行:不需要判斷怎麼取,直接跑
- 精準:只抽出那一節,不是整檔
還有一個實務陷阱:搬完要用那個指令實跑一次,確認命中而且只命中一節。 節名互為前綴時會一起命中——例如 Step 4B 與 Step 4B0,抽節腳本會把兩節都吐出來。
鐵則四:改 skill 前先給使用者看提案
這條是流程性的,但很重要:改 SKILL.md 會影響未來每一次執行的行為,這不是可以自作主張的事。
所以完整流程停在中間:
Step 1 解析 RUNLOG(跑 parse 腳本,看計數、門檻、分群線索)
Step 2 分群 + 判型(主 agent 親手,不委派)
Step 3 草擬提案(明確到「改哪個檔、加哪一句、放哪一段」)
Step 4 ⏸ 給使用者確認 —— 停在這裡
Step 5 套用 + 標記
Step 6 回報
Step 2 特別標了「主 agent 親手」。分群是這整套裡唯一需要真正判斷力的一步,把它委派給子 agent 會得到一份看起來合理、實際上按表面症狀分類的結果——而真正有用的分群是按根因分。
反事實診斷:分群的檢驗方法
Step 2 有一個很好用的技巧。對每個要消化的群,寫一句反事實診斷:
「當初若有 X 這條規則,這幾筆會不會就不發生?」
X 就是你要加的那條規則。
這個問法的價值在於它會自動篩掉沒用的規則。如果你寫不出一條讓那幾筆都不發生的 X,代表你的分群是錯的——那幾筆的根因其實不一樣,該拆開。
鐵則五:消化 = 真的改了東西
最後一條是驗收條件,前面說過但值得重複:
只換標記卻沒動
SKILL.md/ probe,不算消化,禁止。
而且對 probe 腳本的改動有額外要求:用當初出事的輸入重跑驗證,再宣告消化。
這聽起來理所當然,但很容易跳過——因為改完腳本你「覺得」它會對。當初出事的輸入是免費的迴歸測試,不用白不用。
還有一個順手要做的動作:回收既有膨脹。動到某一段時,如果附近有前幾輪留下的案例長敘事,順手壓成一條通則。這樣文件才會在「每輪加一條」的同時維持穩定大小。
回報時附上改前/改後的 bytes,證明這輪沒有淨膨脹。
RUNLOG 與產物的對照表
一個實務細節:不同 RUNLOG 的「產物」不一樣,要先講清楚,否則消化時會不知道該改哪。
| RUNLOG | 產物(要改的檔) |
|---|---|
| 調查型 skill 的 RUNLOG | 該 skill 的 SKILL.md、templates.md、各支 probe 腳本 |
| 審查型 skill 的 RUNLOG | 專案的雷區文件(漏報補這裡,審查器自動跟上)、SKILL.md、helper 腳本 |
| repo 層級的 RUNLOG | .claude/commands/*.md、各支 pre-commit 檢查腳本 |
第二列有個很漂亮的性質:那支審查 skill 執行時會直接讀專案的雷區文件組 prompt。所以「審查器漏抓了某類問題」的消化動作是——在雷區文件補一條,下次執行自動跟上,SKILL.md 一個字都不用動。
當你的工具是讀資料驅動的,消化成本會低一個數量級。 這值得在設計 skill 時就先考慮。
實際跑起來是什麼感覺
盤點:
python parse_runlog.py --all # 掃所有 RUNLOG,看誰達門檻
python parse_runlog.py <路徑> # 細看某一支
輸出會給你:計數、是否達門檻、分群線索、逐條未消化全文,以及各 SKILL.md 的常駐大小。
(一個小細節:解析腳本要排除被 ``` 圍起來的格式範例,否則檔頭的格式說明會被算成一筆條目。這種小坑本身就是第一批進 RUNLOG 的東西。)
然後是分群、提案、確認、套用。一輪大概處理 5–8 筆,產出 2–3 條規則。
73 筆消化下來,SKILL.md 從沒超過 20KB。 這不是因為規則少,是因為每輪都在做「加一條、壓一段」的平衡。
為什麼這件事值得做
回到最開頭的問題:為什麼要這麼麻煩?
因為 AI agent 的失誤有很強的重複性。它不會累,但也不會學——你這次手動補救的那個判斷,下次同樣情境它還是會漏掉,除非你把那個判斷寫進它每次都會讀的地方。
而「寫進它每次都會讀的地方」這件事有成本上限(token)、有品質上限(規則太多等於沒規則)。所以你需要一套機制,決定什麼值得寫進去、寫多短、什麼時候該把舊的壓掉。
RUNLOG 就是那套機制。它把「AI 又出錯了」從一個令人沮喪的事件,變成一筆有明確處理流程的原料。
一句話總結:
親手讀失敗軌跡做錯誤分析,才校準得了工具。
相關文章: