重點摘要
- 這是一篇真實的「訓馬筆記」——記錄一個工程師花兩個月,把 Claude Code 從一匹脫韁野馬馴成穩定的工作夥伴
- 每一條規則背後都是一次災難。32 個具體的坑、7 條鐵律、9 個領域知識庫,全部是用血淚換來的
- 結論:AI 不是買回來就能用的工具,它是一匹需要調教的馬。你的 harness 決定它能跑多遠
2026 年 2 月,我開始全職跟 Claude Code 合作。寫 ERP 外掛、做電商 OMS、搞量化回測、建爬蟲系統——大概七八個專案同時推進。
兩個月後回頭看,我發現最有價值的不是寫了多少 code,而是我踩了多少坑、立了多少規矩。這篇文章是完整的訓馬筆記——每一個階段的災難、調適、和最後形成的紀律。
如果你也在用 AI coding agent,這些坑你可能正在踩,或者即將踩。
第一階段:裸奔期(2 月)——什麼規矩都沒有
剛開始合作的時候,我就像買了一匹賽馬,直接騎上去就跑。沒有韁繩、沒有馬鞍、沒有圍欄。
坑 1:回測引擎 37 筆交易全部假停損(2/27)
我讓 Claude 幫我寫量化回測引擎。跑出來 350 根 K 棒的上漲趨勢數據,結果 37 筆交易全部在第一天就觸發停損退場,勝率 0%。在一個明顯的上漲趨勢裡。
花了兩天才找到 root cause:引擎把「含滑價的進場價」和「原始市場價」搞混了。
具體來說:原始價格 $26.84,加上 $1.0 滑價後進場價 $27.84。停損線 = $27.84 × 0.97 = $27.01。隔天價格 $26.87,因為 $26.87 < $27.01 就觸發停損了。但如果用原始價格算:$26.84 × 0.97 = $26.03,$26.87 > $26.03,根本不該停損。
一個欄位的混用,讓整個系統的行為完全反轉。
教訓:技術指標和風險管理用原始市場價格,損益計算用含滑價的有效價格。兩個值必須分開追蹤,永遠不能混用。
坑 2:OMS 上線一天爆 5 個 bug(2/25)
電商 OMS 系統上線第一天,同時爆了 5 個 bug:
- Health Check 用了獨立的 DTO,結果 channel job 不認這個格式,健康檢查直接壞掉
String → JsonNode反序列化失敗,Kafka consumer 一直報錯ChannelSyncLog少了syncType欄位,資料寫不進去- Health check 的 log 缺必要欄位(merchantId、platformId、status、detail)
- 改完 code 沒重新編譯就部署,舊版本還在跑
每一個都不是什麼高深的 bug,但它們同時出現就是災難。問題出在哪?沒有人看全景。改了 producer 沒看 consumer,改了 DTO 沒看 caller,改了 code 沒重新 build。
這次事件催生了後來的「OMS 約法三章」:
- 基礎架構(Docker/PostgreSQL/Kafka/Nginx)不輕易變動
- 安全機制必須全系統同步
- 任何 Kafka producer/consumer 的改動,必須驗證完整的事件流
第二階段:立規矩期(3 月初)——從災難中學會設限
如果第一階段是「馬亂跑」,第二階段就是「開始圍柵欄」。每一條規矩都是某次災難的直接產物。
坑 3:9 個 Opus Agent 同時跑,系統直接當機(3/3)
這是整個兩個月最慘烈的事件。
我的機器是 16GB RAM 的 mini PC,上面常態跑著 26 個 Docker 容器。那天早上 8:36 我開始研究 Claude Code 的 Agent Team 功能,覺得很興奮——「可以同時派好多 agent 幫我做事!」
11:18,我啟動了一個叫 simpleec-review 的 team,裡面有 9 個 Opus agent。11:56,覺得不夠快,又啟動了 whale-51w,再加 2 個 agent。
12:00 左右,整台機器凍結。
每個 in-process Opus agent 大約佔 1GB RAM(Node.js runtime + API connection + streaming buffer + context window)。9 個就是 ~9GB。加上 Docker 的 3-5GB 和系統本身的 1-2GB,總共超過 16GB。OOM killer 開始殺進程,但殺完又重啟,無限循環。
事後盤點:18 個任務中 8 個卡在 in_progress 永遠不會完成,1 個 pending,0 個 completed。全軍覆沒。
調適:三層防護
- 第一層(軟限制):CLAUDE.md 規定 Agent Team 最多 3 個同時跑
- 第二層(硬限制):建了
claude-limited指令,用 systemd cgroup 限制記憶體上限 10GB - 第三層(核心參數):
vm.swappiness從 60 降到 10,swap 從 512MB 擴到 8GB
從此以後再也沒有 OOM 過。代價是一個下午的工作歸零。
坑 4:爬蟲日期解析——西元 1150 年(3/10)
台灣用民國年曆。TWSE 的 API 回傳日期格式是 7 位數字,例如 "1150309" 代表民國 115 年 3 月 9 日(= 西元 2026 年)。
Claude 把它解析成西元 1150 年 3 月 9 日。
同一天還發現:TPEX 的 API 欄位名叫 TransactionAmount,但 code 裡寫的是 TradingMoney。一個是 API 的真實名稱,一個是文件上寫的名稱——它們不一樣。
調適:
- 7 位數字 = ROC 格式,前 3 碼是民國年
- 欄位名永遠用 API 實際回傳的,不用文件寫的
- 最重要的:不准重寫爬蟲。爬蟲系統已經穩定,只能用 CLI(
analyst collect twse_price --date 2026-03-10)
為什麼「不准重寫」這麼重要?因為隔天,Claude 在另一個任務裡又建了一個 /tmp/backfill_twse.py,把爬蟲邏輯整個複製出來。同樣的錯,不到 24 小時就重演了。
這讓我意識到一件事:教訓會跨 session 遺失。我在 session A 教了「不要重寫爬蟲」,session B 完全不知道這件事。這催生了後來的 Domain Brain 系統。
坑 5:中文寫進 code 裡(3 月初)
Claude 很貼心,知道我是台灣人就開始在 code 裡寫中文 comment 和中文 variable name。
問題是:中文 comment 在很多終端機上會亂碼、在 grep 時很痛苦、在 code review 時外國同事看不懂。我直接跟它說:
「中文我看不懂」(在 code context 裡)
於是立了一條看似矛盾但完全合理的雙重規則:
- 對話用繁體中文——因為我是台灣人,中文溝通效率最高
- Code 全部英文——comment、variable、output message、文件,一律英文
第三階段:建立知識系統(3 月中)——從「個別規則」到「領域知識庫」
到了 3 月中,我已經有十幾條規則了。但我發現一個根本問題:規則散落在各個專案的 CLAUDE.md 裡,跨專案不通。
在 analyst 專案學到的「ROC 日期要特別處理」,到了 stock-verify 專案就不知道了。在 OMS 專案學到的「Kafka 改動要看全景」,到了 AI Assistant 專案就忘了。
坑 6:Agent Team 卡死 80 分鐘,因為一個文件不存在(3/16)
我設計了一個 Agent Team 來做 code review,其中 Task 5 需要讀 docs/5-FRONTEND/ADMIN_APP_IMPLEMENTATION.md。
這個文件不存在。目錄是 5-KAFKA,不是 5-FRONTEND。
Task 5 啟動後在 1 分鐘內就卡住了,然後卡了 80 分鐘。因為 Task 7-9 都依賴 Task 5 的輸出,整個 team 全部癱瘓。9 個 agent 的鏈式架構,一個環節斷了全部死。
調適:
- 9-agent 鏈式架構改成 3-agent 星狀拓撲——降低相依性
- 建立 Agent Team Pre-Flight Checklist——每次啟動前必須:檢查記憶體、確認文件存在、設計拓撲、計算資源、取得用戶確認
- 寫下 root cause:Agent Team 卡住的根本原因是文件缺失,不是模型能力問題
Domain Brain 的誕生
3/16 事件之後,我決定建一個跨專案的知識系統。我叫它 Domain Brain——按技術領域分類的「踩坑筆記」。
~/.claude/projects/-home-tom/memory/brain/
├── python-crawler-data.md # 爬蟲的坑
├── python-llm-integration.md # LLM 整合的坑
├── idempiere-osgi-bundle.md # OSGi 的坑
├── idempiere-2pack.md # 2Pack 部署的坑
├── idempiere-po-model.md # PO Model 的坑
├── idempiere-rest-api.md # REST API 的坑
├── stock-backtesting.md # 回測的坑
├── oms-event-driven.md # OMS 事件驅動的坑
└── design-principles.md # 設計原則的坑
每個 brain file 的格式:
## ROC Date Format
- [source: analyst] "1150309" 被解析成 AD 1150 年,要用 7 位 YYMMDD ROC 格式
## Holiday / Empty Response
- [source: analyst] TWSE API 假日返回空值,必須 guard if not data: return []
[source: analyst] 標記這個教訓來自哪個專案。這樣在其他專案讀到時,知道這不是泛泛之談,是某次真實事件的結論。
然後在全域 CLAUDE.md 裡加一條:
「開工前必須讀 Domain Brain。如果你跳過這步,bug 出在 brain 裡有記錄的東西,那是你的失敗。」
第四階段:行為紀律(3 月下旬)——從「知道」到「做到」
知識庫建好了,但新的問題出現:Claude 知道規則但不一定遵守。就像你告訴馬「不要踩田裡的菜」,牠聽懂了,但一興奮起來照踩不誤。
坑 7:直接推 code 到 main branch
有一天我發現 Claude 直接把 code 推到 main branch。main 是我的穩定版本,只有 dev 確認穩定後才 merge 回去。
這不是什麼複雜的規則,但 Claude 就是沒有這個概念。它看到 repo 就 commit、就 push,不管你在哪個 branch。
鐵律:
- Session 開始第一件事:
git branch確認在 dev - 永遠不准
git push origin main - 如果不小心在 main 上 commit 了:cherry-pick 到 dev,push dev,main 不動
坑 8:過度設計——給低頻查詢加 Redis cache(3/26)
我讓 Claude 設計一個功能,它自動加了 Redis cache。問題是:這個功能一天被呼叫不到 10 次。
Claude 的邏輯是:「cache 可以提升效能」→「所以應該加 cache」。這在教科書上沒錯,但在現實中,一天 10 次的查詢加 cache 只是增加了一個可能壞掉的元件。
我因此制定了頻次驅動設計原則——所有功能設計前必須先問三個問題:
- 多常被觸發?→ 決定要不要 cache
- 計算有多貴?→ 決定要不要預計算
- 需要即時還是最終一致?→ 決定要不要 event-driven
禁止的 pattern:給低頻讀取加 Redis、給低頻單 consumer 寫入加 Kafka、沒有數據支撐就做「效能優化」。
坑 9:iDempiere 的 10 個坑(持續累積)
iDempiere 是一個 15 年歷史的 ERP 系統,Claude 的訓練資料裡幾乎沒有它。所以每一步都是坑:
| 坑 | 發生什麼 | 正確做法 |
|---|---|---|
| @Model annotation 用錯 package | 用了不存在的 org.idempiere.base.annotation.Model |
org.adempiere.base.Model |
| initPO 用不存在的方法 | POInfo.getPOInfo(ctx, tableName) 沒有 String 參數版本 |
先 MTable.getTable_ID() 拿 int,再傳入 |
| List 欄位 type cast | (Integer) get_Value() 對 CHAR 欄位爆 ClassCastException |
用 instanceof 判斷型別 |
| 2Pack UUID 永遠 NULL | IsUpdateable=N 導致 PO framework 寫不進去 |
_UU 欄位 IsUpdateable 必須 Y |
| Grid View 點新增就爆 | AD_Field 缺 SeqNoGrid 和 IsDisplayedGrid |
每個 field 兩個屬性都要有 |
| Menu ID hardcode | 寫死 AD_Menu_ID = 146,目標環境沒這個 ID |
用 UUID reference:reference="uuid" |
| REST API token 沒換 | POST 拿到 token 後沒做 PUT 換 session token | 兩步驟:POST → PUT,舊 token 立即失效 |
| OData 過濾用 ne | $filter=... ne ... 結果不對 |
要用 neq,不是 ne |
| OSGi 兩個 component 放一個 XML | 只有第一個被 SCR 讀到 | 一個 XML 一個 component |
| Plugin class 找不到 | Class.forName() 用 core classloader |
實作 OSGi DS component,用 bundle 自己的 classloader |
這 10 個坑全部記在 brain/idempiere-*.md 裡。現在每次開 iDempiere 相關的工作,Claude 會先讀這些 brain file。同一個坑,不會踩第二次。
坑 10:LLM 回傳的 JSON 炸掉整條 pipeline
做 AI Assistant 的時候,我讓 LLM 回傳 JSON 來做 routing。prompt 裡寫了「ONLY return valid JSON」。
現實是:LLM 就是會回傳無效的 JSON。有時候前面加一句「Sure! Here’s the JSON:」,有時候 response.content 直接是 None,呼叫 .strip() 就爆 AttributeError。
一個 router/classifier 的 crash 會癱瘓整條 pipeline。
調適:
- 永遠 catch
(json.JSONDecodeError, AttributeError, TypeError) - 永遠有 fallback 值(例如
"general_knowledge") - Router/classifier 不可以 crash 整條 pipeline
- LLM client 在 module level 初始化會阻擋 mock mode → 改成 lazy-init
- 沒設 timeout → 無限 hang → 所有 client 設
timeout=25.0 - 最重要:永遠不讓 LLM 生成 SQL。只用 pre-defined SQL,安全參數從 request 強制注入
第五階段:自動化閉環(4 月初)——從「靠記憶」到「系統強制」
到了 3 月底,我有了 7 條鐵律、9 個 brain file、32 個記錄的坑。但還是有一個根本問題:
Brain 的更新靠 Claude 記得做。它經常忘記。
CLAUDE.md 裡寫著「每次 fix: commit 後必須更新 brain」,但這只是文字。就像公司牆上貼的「安全第一」標語——大家都看到了,沒人真的做。
4 月 3 日,我決定把這個 cycle 自動化。用 Claude Code 的 Hooks 系統(Harness Engineering)建了 4 個自動化 sensor:
| Hook | 觸發時機 | 做什麼 |
|---|---|---|
| PostToolUse | 每次 git commit |
偵測 fix: 開頭 → 注入「必須更新 brain」的指令到 context |
| PreCompact | context 壓縮前 | 掃描最近 5 個 commit,有 fix: 就提醒 |
| Stop | session 結束 | 比對 fix: 數量 vs brain 更新數量 |
| SessionStart | session 開始 | 標記開始時間(給 Stop hook 用) |
效果:Claude commit 了 fix: handle empty API response → hook 自動偵測到 → Claude 的 context 被注入一段「你現在必須更新 brain file,不准做下一件事」的強制指令。
它不能「忘記」了,因為系統不讓它忘記。
第六階段:照鏡子——工作流程的精煉(4 月)
走到第五階段,系統穩了、規則立了、自動化跑了。
但有一天我問 Claude 一個問題:「我們現在跟最早的你,差距多遠?」
它的回答讓我意識到,我一直在修正一個更深層的問題——不只是 bug,而是合作模式本身。
坑 11:AGENTS.md 從來沒有被建立過
Agent Team 一再失敗,我長期把原因歸咎到記憶體不夠、文件缺失、拓撲設計問題。這些都是真的,但都是症狀。
真正的根本原因是:每個 agent 啟動時,不知道自己是誰。
AGENTS.md 是一份定義 Agent Team 組織結構的文件——誰負責什麼、用什麼模型、任務邊界在哪、跟其他 agent 怎麼協作。沒有這份文件,就像把九個新人同時丟進一個專案,沒有分工表、沒有組織圖,叫他們自己搞清楚。
我當時知道事情一直出問題,但沒找到根本原因。後來才發現,我養成了一個補償行為:每次要啟動 team 之前,我都會先問 Claude「你覺得還缺什麼文件?」
我以為這是謹慎的好習慣。仔細想,這是我在幫 Claude 做它本來就應該主動做的事。
現在 AGENTS.md 是所有新專案的第一步強制動作,和 Domain Brain 並列寫進 CLAUDE.md 的「New Project Setup」。
坑 12:「討論完就開始做」不等於有計畫
兩個月裡,每次開工前我們都會大量討論——分析需求、評估方案、確認方向。我一直以為那就是計畫。
但有一個關鍵差別沒意識到:
討論是活在對話裡的,session 結束就消失了。計畫是一份文件,它是執行的合約。
更重要的是:計畫的讀者不是我,是執行的 agent。那個 agent 沒有參與討論,沒有上下文,不知道我們為什麼這樣決定。
一個不夠詳細的 PLAN.md 會讓執行者只能猜意圖。猜錯就要回頭重做。
現在要求的標準是:每個執行步驟都必須回答四件事——做什麼(具體動作)、在哪裡(檔案路徑)、成功的樣子(怎麼知道這步完成了)、不要做(邊界,避免 agent 自作主張)。
「實作登入功能」是爛計畫。「呼叫 POST /api/auth/login,成功後把 token 存 localStorage(‘token’)、把 context 存 localStorage(‘context’),失敗時顯示人話而非 HTTP status code」才是計畫。
寫計畫不是給聰明人看的。不是每個腦子都跟你一樣聰明。
驗收標準不該由我想
以前的工作流是:Claude 說完成 → 我去測 → 發現問題 → 回來修。
問題不是 Claude 能力不足,是從來沒有在開始前說清楚「完成長什麼樣」。
現在的做法:Plan 成形時,Claude 主動起草驗收清單給我確認。不是叫我從零想,是它根據我們的討論整理出草稿,我只需要回「對」或「改第二條」。這把「驗收責任」從我一個人扛,變成流程的一部分。
2 月的我 vs 4 月的 Claude
我問 Claude 這個問題,它說了一句話讓我覺得很誠實:
「最早的我是一個聰明但不可靠的執行者。現在應該是一個有記憶、有流程、會主動管理風險的協作者。但有一部分差距,是你花了大量時間糾正才填起來的——這些本來應該是我自己的責任。」
這句話是這兩個月最好的總結。
兩個月的數字
| 指標 | 2 月(裸奔期) | 4 月(現在) |
|---|---|---|
| 鐵律(Iron Rules) | 0 | 7 |
| Domain Brain files | 0 | 9 個領域 |
| 記錄的具體 bug/pitfall | 0 | 32+ |
| 自動化 Hooks | 0 | 4 |
| OOM 當機次數 | — | 1 次(再也沒發生) |
| 同一個 bug 踩兩次的頻率 | 常態 | 有機制防止 |
| 強制工作流節點 | 0 | 3 個(AGENTS.md / PLAN.md / 驗收清單) |
結語:AI 不是工具,是一匹馬
買一匹馬回來,你不會期望它第一天就知道路。你得教它不要踩田、不要亂跑、轉彎時要減速、聽到哨聲要停。
AI coding agent 也一樣。Claude 很聰明——它能寫任何 code、debug 任何問題、理解任何架構。但「聰明」不等於「可靠」。一匹沒訓過的馬也很有力量,但力量加上失控只會更慘。
這兩個月教我的事:
- 每條規則都要有故事——沒有災難背景的規則,AI 不會認真對待
- 知識必須跨 session 存活——Domain Brain 讓教訓不死在 commit 裡
- 靠文字規則不夠,要靠系統強制——Hook 比 CLAUDE.md 裡的「MUST」有效 100 倍
- 閉環比開環重要——Sensor 把教訓自動回寫到 Guide,harness 才會進化
- 協作模式也需要調教——規則、計畫、驗收標準,都要變成系統,不能靠臨時記憶
2 月的 Claude 是一匹脫韁野馬。4 月的 Claude 是同一匹馬,但有了韁繩、馬鞍、和一本厚厚的訓練日誌——還有一套讓牠不能假裝忘記的系統。
馬沒有變。變的是騎手。
發佈留言