訓馬筆記:兩個月把 Claude Code 從脫韁野馬馴成工作夥伴的完整紀錄

重點摘要

  • 這是一篇真實的「訓馬筆記」——記錄一個工程師花兩個月,把 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:

  1. Health Check 用了獨立的 DTO,結果 channel job 不認這個格式,健康檢查直接壞掉
  2. String → JsonNode 反序列化失敗,Kafka consumer 一直報錯
  3. ChannelSyncLog 少了 syncType 欄位,資料寫不進去
  4. Health check 的 log 缺必要欄位(merchantId、platformId、status、detail)
  5. 改完 code 沒重新編譯就部署,舊版本還在跑

每一個都不是什麼高深的 bug,但它們同時出現就是災難。問題出在哪?沒有人看全景。改了 producer 沒看 consumer,改了 DTO 沒看 caller,改了 code 沒重新 build。

這次事件催生了後來的「OMS 約法三章」:

  1. 基礎架構(Docker/PostgreSQL/Kafka/Nginx)不輕易變動
  2. 安全機制必須全系統同步
  3. 任何 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 只是增加了一個可能壞掉的元件

我因此制定了頻次驅動設計原則——所有功能設計前必須先問三個問題:

  1. 多常被觸發?→ 決定要不要 cache
  2. 計算有多貴?→ 決定要不要預計算
  3. 需要即時還是最終一致?→ 決定要不要 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_FieldSeqNoGridIsDisplayedGrid 每個 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 任何問題、理解任何架構。但「聰明」不等於「可靠」。一匹沒訓過的馬也很有力量,但力量加上失控只會更慘。

這兩個月教我的事:

  1. 每條規則都要有故事——沒有災難背景的規則,AI 不會認真對待
  2. 知識必須跨 session 存活——Domain Brain 讓教訓不死在 commit 裡
  3. 靠文字規則不夠,要靠系統強制——Hook 比 CLAUDE.md 裡的「MUST」有效 100 倍
  4. 閉環比開環重要——Sensor 把教訓自動回寫到 Guide,harness 才會進化
  5. 協作模式也需要調教——規則、計畫、驗收標準,都要變成系統,不能靠臨時記憶

2 月的 Claude 是一匹脫韁野馬。4 月的 Claude 是同一匹馬,但有了韁繩、馬鞍、和一本厚厚的訓練日誌——還有一套讓牠不能假裝忘記的系統。

馬沒有變。變的是騎手。

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *