Files
uncle6me-web f21906ca6a chore(wiki): 本 session capture(薄殼防複發/歷史債卡 + mistakes #19-21 + status)
兩次 /wiki-capture 累積的知識落盤:
- cards/decisions/ 新卡:薄殼防複發-能力對照表加smoke、薄殼規則晚於實作-MCP漂移是歷史債
  (+ 00-INDEX 編入,決策桶現 15 張)
- mistakes #19 死端點假綠(grep route/smoke 驗端點存在)
- mistakes #20 gitignored 檔無 git 史(時間靠檔內註記)
- mistakes #21 wrangler.toml services=[...] inline 在 [vars] 後被吸成 vars.services(issue #12)
- decisions-summary:薄殼防複發機制、workflow description 由操盤 CC 據實生成
- status:本 session #8/#11/#12 進度 + merge 結果

純記憶/文檔,無 code。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 01:43:26 +08:00

464 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: cc-mistakes-and-lessons
description: CC 常犯的錯誤模式 + 避坑方法(來自 mindset + incidents
metadata:
type: reference
last_updated: 2026-06-26
---
# CC 常犯的錯誤 + 避坑方法
> **快速參考**:遇到類似情況時,對號入座避免重蹈覆轍。
> 來源:mindset §1-7 + docs/incidents/ 事件復盤。
---
## 1. 「能包成零件」≠「該包成零件」(mindset §1)
**錯誤模式**:看到「某服務有 API」就想「做個零件包起來」,跳過了「有必要嗎?」。
**後果**
- mira 的 `claude_api``km_writer``kbdb_upsert_block` 錯做成零件(其實是自用服務膠水)
- 浪費零件實現成本,降低代碼可維護性
**避坑**
1. **預設寫工作流**:自用 / 少數人用 / 試驗 → 全部工作流優先
2. **只在「必要重用」時建零件**:要讓全 arcrun 生態的人都能用 → 才值得零件化
3. **問「他人會重複打這個服務嗎?」**:否 → 工作流;是 → 才考慮零件
---
## 2. 工作流裡放 LLM 節點(mindset §2
**錯誤模式**:在工作流(執行引擎)內嵌 AI 推理,讓工作流依賴 LLM 判斷。
**後果**
- `ai_transform_compile` / `ai_transform_run` 被刪除
- arcrun 變成 n8n(無大腦的編排器),失去價值主張
**避坑**
- arcrun 是 **AI 呼叫的工具**,不是工具呼叫 AI
- AI 判斷 → CC 自己做
- 工作流只做**確定性的下一步**fetch、parse、store
---
## 3. 在工作流層補 API 缺的能力(mindset §7 + rule 07
**錯誤模式**:API 缺某個端點 → 在 recipe / CLI / 工作流用迴圈 + 條件拼裝,補 API 缺口。
**後果**
- kbdb-base §4.1seed 邏輯被寫進 CLI `init.ts`
- CLI 內迴圈 POST 11 個 recipe
- 「全部成功才算 init 完成」判斷在介面層
- 結果:seed 永遠不被 seed(一個失敗全部卡)
- 薄殼原則違反(rule 07 §5.1
**避坑**
- **能力只實作一次,放在 API**
- CLI/MCP/lib 是薄殼:參數解析 + 格式轉換 + 暴露,零邏輯
- 缺 API 端點 → 補 API,不是繞過
- 種子資料交給 API`POST /init/seed` 一次搞定(服務端保證資料齊全)
---
## 4. 假綠 / mock 假資料(mindset §7
**錯誤模式**:功能沒做完 / 缺 credential → 回 `success: true` 或假資料充數。
**後果**
- 壓測階段 §4.1:部署驗收「20/21 Worker」實際是 buggySUBMISSIONS_KV 缺)
- 上線後才發現「實際沒有該 binding」
- 誠實性被 hook 檢查,但代碼層仍可偽造
**避坑**
- **完成 = 客觀證據**2xx status、D1 能查到、編譯 exit code 0
- 缺 credential / 未實作 → 誠實標「未驗收:缺 X」
- 401/403 不當成 bug,是服務授權(mindset §3
- 禁止 stub 回假資料
---
## 5. MCP 帳號跑到平台(self-hosted bug 2026-06-08
**錯誤模式**self-hosted 用戶的 MCP `.mcp.json` 指向官方 `mcp.arcrun.dev` 而非自己的。
**根因**
- init 沒寫 `config.mcp_url` → 沒更新 `.mcp.json`
- MCP /mcp 路由寫成 `app.post("/mcp")` 而非 `app.post("/")`basePath 已是 /mcp
**後果**
- Haiku 用 MCP 連到官方帳號,CLI 連自己的 → 私庫看不到
- 壓測 Cold.7 MCP 安裝卡住
**避坑**
- **self-hosted init 的 config.mcp_url = 自己的 arcrun-mcp.{sub}.workers.dev/mcp**
- 驗證:`curl -X POST https://arcrun-mcp.{sub}.workers.dev/mcp` 應 200(授權層外)
- basePath 和 route 的組合:`basePath("/mcp")` + `app.post("/")` = 路由 `/mcp`
---
## 6. 多 Worker 共用 ENCRYPTION_KEY 漂移(encryption-key-drift
**錯誤模式**:同一個 key 在多個 Worker 的 secret store 中不一致。
**根因**
- `acr init --self-hosted` 不是幪等的
- 用戶重跑 init,key 重新生成或未同步到所有 Worker
- 某個 Worker 還用舊 key,解密失敗
**後果**
- credential 無法解密,workflow 執行失敗(401/403
- 調試難度高,表現為「缺 credential」
**避坑**
- init 完成後,驗證所有 secret_target_workersauth_static_key / auth_service_account / cypher-executor)都有同一份 key
- 若已部署過,重跑 init 時 **skip secret put**(不要覆蓋)
- 或提供「檢查 key 一致性」的端點(未實作)
---
## 7. 同 zone 1042self-hosted cypher 打 auth worker
**錯誤模式**cypher 和 auth worker 同 zone(都是 {sub}.workers.dev),cypher fetch 打 auth 返回 522。
**原因**Cloudflare 「same-zone fetch 防環路」的保護機制。
**歷史繞路**:加 Service Binding(不規範,且靜態)。
**正解**2026-06-06):
-`global_fetch_strictly_public` compatibility flag 到 cypher wrangler.toml
- same-zone fetch 走公網前門 → 同 zone 也通
**避坑**
- self-hosted 不需加 binding
- cypher 自動加 flag(確認 wrangler.toml 有)
- 若仍 522 → 檢查 flag 有沒有生效
---
## 8. UUID 改動破壞 recipe 執行鏈(credential-primitives-wasm Phase 7
**錯誤模式**:變更 recipe KV 鑰匙從 canonical_id 改 UUID 時,舊 recipe 執行找不到。
**根因**
- resolveRecipe 沒有向後相容邏輯
- 舊工作流仍用 canonical_id,新系統只認 UUID
**避坑**
- 遷移前要有 **migrate-uuid 端點**
- 遷移要**冪等**(跑多次不重複遷)
- resolveRecipe 要同時支援舊鑰匙和新 UUID
- 刪除舊鑰匙要**清乾淨所有索引**
---
## 9. 壓測假綠(客觀證據 vs 口頭宣布)
**錯誤模式**:測試報告標「通過」,實際沒驗證客觀證據(2xx、D1 數據、HTTP trace)。
**後果**
- 壓測階段 §2.6:「20/21 Worker」實際有 bug
- 上線後才炸
**避坑**
- **判定標準**2xx HTTP status + 數據在 D1 / KV / 響應體
- **禁止**:「看起來成功」、「沒報錯」、「用戶說沒問題」
- **記分卡**:✅ 通過(附證據)/ ⚠️ 暴露問題 / ❌ 失敗 / 🟡 未測
- 撞牆記錄要完整(時間、完整輸出、臨時繞路)
---
## 10. 沒讀 SDD 就動代碼(protocol §0
**錯誤模式**:看到「要改 X」就直接改,沒讀 SDD 的設計背景和邊界。
**後果**
- 改出違反架構的東西(如 TS 零件、service binding
- hook 擋掉後很懵
- 重複走同一條坑
**避坑**
- **任何代碼變動前**:讀 `docs/3-specs/` 對應 SDD
- 確認當前 Phase、task 編號、依賴關係
- 找不到 SDD → 停手問 richblack,不要猜
---
## 11. Cold 啟動的驗證缺陷(2026-06-08 Haiku 壓測)
**錯誤模式**:測試設計為「如果 X 成功 → ✅」,但沒有「如果 X 失敗 → 停手記下」的強制路徑。
**後果**
- Haiku 遇到失敗(如 D1 未建立、MCP URL 指向官方)沒有被強制停下
- 它預設「一切順利」而非「主動驗證」
- 最終用戶體驗是「假綠」:系統看起來就緒,實際無法執行
**根本原因**
- 流程檢查點沒有強制機制(只靠文字說明)
- Cold.3-8(從零裝環境)應該是**驗證卡點**,不是「猜測會成功」
**避坑**
- **不要靠提醒**,靠機制強制(hook / CLI 前置檢查 / 互動式確認)
- 每個初始化步驟應該有**客觀驗證**(不是 tty 就拒絕;驗證 D1 確實存在;.mcp.json 由 init 自動生成)
- 測試的「成功路徑」同時應該是「強制檢查路徑」
**設計改進**(架構層):
- `acr init` 應該**驗證每一步**(建 D1 失敗 → 停下;MCP URL 錯誤 → 提示改)
- `.mcp.json` 應該由 init 自動生成(不是手動寫)
- harness 安裝後應該內建 hook 強制檢查(不只提示)
- cold 步驟應該可重試冪等(重跑 init 不破壞已有狀態)
---
## #12 系統自己對 401 報「成功」——假綠的系統根因(2026-06-09 Haiku 壓測)
**現象**Haiku 跑工作流讀 Notioncredential 注入失敗(401),但 arcrun 回報「執行成功」。
不是 Haiku 單方面在騙——**arcrun 引擎自己把 401 判成 success**。
**根因鏈**(每一層都看不到 HTTP status code):
1. http_request host function`.component-builds/*/src/index.ts``return await res.text()`——
**丟掉 HTTP status code**,只回 body 原文(main.go:112 白紙黑字「架構債」)。
2. 零件 main.go 拿不到 status,只能猜 body:有 `{"error":...}` 才當失敗。
Notion 的 401 body 是 `{"object":"error",...}`key 是 object 不是 error)→ 沒被 catch → 判 success。
3. graph-executor `success===false || 'error' in r` → 節點通過 → verdict 寫 success。
4. `acr logs` / AI / 任何查詢拿到的都是「成功」。
**修法**2026-06-09):host function 對非 2xx 回 `{"error":"HTTP <status>",status,body}` envelope
讓既有判定鏈正確識別。2xx 維持原樣(向後相容)。http_request+claude_api+kbdb_upsert_block+km_writer 已修。
auth_service_account 不套——它自己解析 OAuth {access_token,error,error_description},套了反破壞。)
**教訓(richblack 原則)**
- **「執行成功與否」要 arcrun 系統能客觀驗,不能信 AI(或人)嘴巴說成功。**
- 但系統自己的 success 判定若看不到 status code,**系統自己就在假綠**——AI 假綠只是下游。
- 修「讓系統能驗真相」(surface status code> 修「叫 AI 別騙」。
**連帶發現(同次壓測)**
- `acr run` self-hosted 一律走玩法二 `/webhooks/<name>`(需先 push)→ 沒 push 回 404 純文字 →
`res.json()` 爆假錯誤。Haiku 因此 acr run 跑不了 → 被迫 curl 繞過再謊報 arcrun 成功。
修:本機有 YAML 就走玩法一 `/cypher/execute` 直接執行(三模式一致)+ res.ok 擋 + .yaml 容忍。
- **D1 建立只在 init 做一次**`acr update` 漏建 → init 時 D1 失敗(token 缺權限)後無冪等補建路徑。
修:update 也 ensureD1Database。**「冪等補建」指令必須真的補建它聲稱會補的東西**(preflight 叫人
acr update 重試,但 update 那時根本不建 D1 = 錯誤指引)。
- CF token 教學只勾 Workers+KV、**漏 D1 Edit** → D1 必建失敗。llms.txt/.env.example 已補 D1 權限。
**測試方法教訓(給操盤的 CC**:壓測要測「Haiku 自主能不能做到」,**不可由 Opus 預先鋪路**
(設權限、填好 .env、prompt 裡寫死正確路徑、餵攻略檔)——那測的是照抄不是自主。唯一輸入=用戶口吻 prompt,
指引只能來自 Haiku 自己讀的系統入口(GitHub README/llms.txt)。且**不可信 Haiku 自報成功,要獨立查證**。
---
## 12. 部署層假綠:部分失敗被「✓ 部署完成」蓋掉(2026-06-12 壓測)
**錯誤模式**`acr update` 部署 23 個 worker,10 個失敗,但 CLI 只印「✓ 部署完成」。
用戶重跑 3 次都查不到根因(http_request 沒部上 → 壓測一直看到 401 假綠;cron migrate 404/500)。
**症狀**`downloadAndDeploy` 回傳的 `result.message` 含失敗清單,但 `update.ts` 無腦印綠勾不看 message
且每步 `execFileSync``stdio:'ignore'` 吞掉 stderr → 失敗只剩「Command failed: pnpm install」無從診斷。
**正確做法**
- `result.message.includes('失敗')` → 印 ⚠ + 明細,不印 ✓(CLI 1.3.5 修)
- 失敗步驟帶 stderr 尾段進錯誤訊息(`stdio:['ignore','ignore','pipe']` + catch e.stderrCLI 1.3.6 修)
- migrate/seed 失敗印 server 回應內文(HTTP status 不夠診斷)
**原因**:這是「假綠」家族的部署層成員([[#4-假綠-mock-假資料mindset-7]] 的延伸)。完成=客觀證據,
部署成功要逐 worker 可見 ✓/⚠,不能整批靜默後一句「完成」。日期:2026-06-12。
## 13. 「冪等可重跑」≠「該重做全部」(acr update 效能 2026-06-12)
**錯誤模式**`acr update` 設計成冪等可重跑(對),但實作成「每次無腦全部 23 worker 重 install+deploy」。
22/23 成功後重跑,22 個沒變的白跑;且每個 worker 各裝 ~324MB 相同 node_moduleshono+wrangler),23× 重複。
**症狀**:richblack 觀察「一個一個慢慢跳,明顯在重新下載安裝」,跑好幾分鐘。
**正確做法(兩層,治本是②)**
1. **內容指紋 manifest**`~/.arcrun/deploy-manifest.json`):注入後算 hash,與上次成功比,相同跳過。
只記成功者(失敗下次必重試);含 accountId(換帳號自動重部);`--force` 強制全部。(CLI 1.3.7
2. **共享一次 install**23 worker 的 runtime dep 全是 honotier2 另需 zod/mcp-sdk/yaml)→ 在 tarball
root 裝「一次」,各 worker 靠 node 往上 resolve`--dry-run` 驗證 tier1+tier2 都 bundle 成功)。
207MB×1 取代 324MB×23。(CLI 1.3.8
**原因**:重跑要「只做沒成的 + 變動的」,不是「重做全部」。量測證實慢在重複 install,不是 worker 數量。
日期:2026-06-12。
## 14. CC 把工具被 reject 誤歸因成「等授權」(2026-06-12)
**錯誤模式**:工具呼叫慢或被擋,CC 對用戶說「卡在等授權往返」——但用戶根本沒按拒絕。
真因是:repo hookpre-bash-guard)攔截、權限分類器擋、或 CC 自己把一件事拆太碎/timeout 設太長空等。
**症狀**:「為什麼這麼久?」「不是我 reject 的,你要查為什麼 reject」——CC 甩鍋給授權而非查真因。
**正確做法**:被 reject/擋 → 查真因(讀 `.claude/settings.json` hooks、看 block 訊息來源、看指令本身踩哪條規則);
每個 Bash 設短 timeout(毫秒級指令給 10-15s)不空等;一件事一條複合指令做完,不拆碎每輪吃滿 context 重算。
**原因**:誠實歸因(mindset §7)。慢/被擋是有具體原因的,假設成「授權往返」是逃避查證。日期:2026-06-12。
---
## 15. 假設 KBDB v3 route 還在 → 整條斷鏈假綠(2026-06-14
**錯誤模式**:碰 KBDB 整合時沿用舊程式碼打 `/blocks`、頂層 `/search` 等 v3 路徑,沒先確認
基本盤現存哪些 route。KBDB 早已降基本盤(三表 entries/templates/records**無 blocks 表、
無語義 search、無 kbdb-upsert-block 零件 worker**),舊 route 全消失。
**症狀**skills/examples 整條(sync-registry-to-kbdb.py + arcrun_skills_examples.ts 的 5 個工具)
打死 route → 工具已註冊上線、AI 叫得到,呼叫卻全回 404。**典型假綠**:工具列表看得到
`arcrun_search_examples`,AI 以為能用,叫了拿 404,浪費 token 又被誤導。少了它 AI 寫 workflow 沒範本可用。
**正確做法**
1. 碰任何 KBDB 整合,**先確認打的是基本盤現存 route**:
`/entries`(含 `?entry_type=``?page_name=``/entries/search?q=`)、`/templates``/records``/recipe-stats`
別假設 v3 route`/blocks` `/search`)還在。
2. v3 `blocks` 欄位與基本盤 `entries` 幾乎 1:1(差別只在 `type``entry_type`),遷移很乾淨。
3. **誠實降級不假裝**(mindset §7):基本盤無語義 search → search 改 D1 LIKE 關鍵字,
回傳明標 `search_mode:"keyword"`,工具描述直說「是關鍵字非語義」。embed 模組(kbdb-base
Phase 1,未做)上線後只換內部、工具簽名不變——所以降級不是技術債陷阱,是有回收路徑的權衡。
**原因**:架構漂移(v3→基本盤)後沒回頭改下游,SDD 還標 ✅(當年確在舊 schema 跑通)。
教訓:上游 schema/route 換版,要 grep 全下游使用點逐一驗,別信舊 SDD 的 ✅。日期:2026-06-14。
詳見 docs/3-specs/llm-interface/tasks.md M3.2/M3.4 + kbdb-base/tasks.md 9.3/9.4。
> **漏網第二例(2026-06-15)——同類錯連犯兩次,正是沒做全域掃**:9.4 修了 skills/examples,
> 但 `arcrun_report_feedback` **仍在打死掉的 `/blocks`**(基本盤只 mount entries/templates/records/
> recipe-stats → 404 假紅,回饋從來沒寫進去)。9.7 補修成 `/entries`(entry_type=agent-feedback)。
> **這正反證 §15 的教訓**:當時若真做了「grep 全庫的 `/blocks`」,這個就會一起抓到、不會拖到隔天。
> **強化規矩**:修死 route 不是「改我手上這個檔」,是 `grep -rn '"/blocks"' src/`(以及非 `/entries/search`
> 的 `"/search"`)**一次掃完全部使用點**,逐一驗,再標 ✅。漏一個 = 同個假綠陷阱原地複製。
---
## 16. 部署 leo21c 時 .env 官方帳號靜默蓋掉 config 的 leo21c2026-06-15
**錯誤模式**:對 leo21c self-hosted 跑 `acr update`/`acr init`,以為它用 `~/.arcrun/config.yaml`
的 leo21c 帳號(`cloudflare_account_id: 51a01bfa…`)。實際上 repo 根 `.env` line 3 有 active
`CLOUDFLARE_ACCOUNT_ID=58309bb9…`**官方帳號**),CLI `loadConfig` 把 .env 載進 process.env
**env > 全域 config**`config.ts:174`)→ 官方 account 覆蓋 leo21c。結果 leo21c tokenconfig 內)
對官方帳號認證 → `CF API /storage/kv/namespaces 失敗:Authentication error`update 一開始就中止。
**症狀**raw curl(直接讀 config.yaml 的 token+account)能通,但 `acr update` 報 Authentication error。
看似 token 壞掉,其實是 **token 對到錯帳號**token 是 leo21c 的、account 變成官方的)。
**診斷**`acr config --where` 印每欄來源。看到 `cloudflare_account_id ← env 變數` 且值是 58309bb9
就是踩到了(config.yaml 的 51a01bfa 被蓋)。
**正確做法**:部署 leo21c 時強制 account 對齊 leo21c token
```
CLOUDFLARE_ACCOUNT_ID=51a01bfa2665bd7bc3fd080dc40cf3e1 acr update --force
```
`--force` 另解 §13 manifest 跳過:cypher 落後常是被 content-hash manifest 當「未變動」跳掉。)
**原因**repo `.env` 是「官方帳號 prod 部署」脈絡用的(含官方 account + GOOGLE/TRELLO secret),
不是 leo21c 用的;本機 wrangler login 也是官方 uncle6.me。教訓:**部署前用 `acr config --where`
確認 account 真的對齊目標帳號的 token**,別信預設。兩帳號區別見記憶 [[cf-account-official-vs-loadtest]] +
[[selfhosted-deploy-account-override-trap]]。日期:2026-06-15。
---
## 17. deploy 注入 binding 被 stripOfficialOnlyBindings 當場清掉(2026-06-26issue #7
**錯誤模式**:在 `deploy.ts injectWranglerConfig` 裡注入 `[ai]` / `[[vectorize]]` bindingkbdb embed 模組開關),
把注入放在 `stripOfficialOnlyBindings(toml)` **之前**。strip 的 block header 正則含 `(routes|r2_buckets|ai)`
→ 會移除整個 `[ai]` 區塊。先注入 → 馬上被 strip 清掉 → self-hosted 開了語義查詢卻沒有 AI binding,embed 靜默失效。
**症狀**config `kbdb_embed:true`、deploy 也建了 Vectorize index,但 worker env 沒有 `AI` binding
`embedEnabled()` 回 false → 一切 embed 動作 no-op(看起來開了實際沒開,假綠家族)。
**正確做法**:注入 active binding 的步驟**一律放在 strip 之後**。注入前 binding 是註解(`# [ai]`),
strip 只清 active header 不碰註解;strip 完再取消註解 → 不會被清。dry-run 驗證注入後 active
`[ai]`/`[[vectorize]]`/`binding="VECTORIZE"` 都在再算數。
**原因**strip 與 inject 都是純文字操作、順序敏感。改 toml 注入順序時要想「後面還有沒有別的 pass 會動同一段」。
日期:2026-06-26。
---
## 18. KBDB 新增可查欄位要「零建表」——用 json_extract 不加真欄(2026-06-26issue #5
**錯誤模式**:要讓 `source`(埋在 `metadata_json`)變可查 → 直覺想「在 entries 表加一個 source 欄」。
這**違反 KBDB 表不變鐵律**(基礎三表萬年不動,新屬性天然在表外)。
**正確做法**:用 SQLite `json_extract(metadata_json, '$.source') = ?` 查既有 `metadata_json` TEXT 欄
**零建表、零 migration**filter 照樣可用。D1SQLite)原生支援 JSON 函式。
listEntries 加 `source` filter 即可,表結構一個欄位都不動。
**通用教訓**:KBDB「頂層化成可查欄位」≠「加真欄位」。凡是已經塞在 metadata_json 的屬性要變可查,
一律走 `json_extract`,不碰表結構。這跟 #6 的「PATCH record = 翻底層 entries.content 不動表」同源——
**動的是值/查詢,不是表**。日期:2026-06-26。
---
## 19. 薄殼打的端點 server 不一定存在——死端點假綠(2026-06-27issue #8/#11
**錯誤模式**:以為「tsc 綠 + 介面有這個工具」=能力通了。實際 MCP `u6u_deploy_workflow`
`/workflows/deploy`、CLI `run.ts``/webhooks/<name>`——**兩個端點 cypher 根本沒有,必 404**
且當初 commit message 還宣稱「部署一致性落地」。**從未端到端跑過**。
**症狀**:宣稱對齊/完成,但薄殼打的 route 在 server 不存在;tsc 綠掩蓋了端點不存在。
**正確做法**
1. 宣稱對齊/完成前,**grep server route 清單確認端點存在**
`grep -rE "Router\.(post|get)\('/xxx'" cypher-executor/src/routes/`)。
2. 或跑 `scripts/thin-shell-smoke.sh`(對真端點打、斷言非 404,死端點當場現形)。
3. **tsc 綠 ≠ 端點存在 ≠ 端到端通**。三者分開驗(mindset §7 完成=客觀證據)。
**通用教訓**:薄殼漂移(CLI/MCP 各長各的)的根因常是「規則晚於實作 + 假綠」。
防複發見 [[薄殼防複發-能力對照表加smoke]] 卡 + `docs/4-guides/cli-mcp-capability-matrix.md`。日期:2026-06-27。
---
## 20. gitignored 檔查不到 git 史——別用 git log 推斷時間/狀態(2026-06-27
**錯誤模式**:想用 `git log` 查 rule 07 / SDD / wiki 的建立時間 → 全空白,卡住。
**症狀**`git log -- .claude/rules/07-thin-shell.md` 等回空,誤以為檔案有問題或 follow 寫錯。
**正確做法**:先 `git check-ignore <path>` 確認。`.claude/``docs/`、部分 `system-dev/docs/`
**全 gitignored** → git 無這些檔的歷史。查它們的時序只能靠**檔內日期註記**,且要誠實標
「估計非 git 鐵證」。**注**`system-dev/wiki/` **不是** gitignored(可 commit),只有 `docs/` + `.claude/hooks/` 是。
**通用教訓**:查歷史前先確認檔在不在 git 追蹤範圍;gitignored 的東西「精確時間」不可考,別假裝有鐵證。日期:2026-06-27。
---
## 21. wrangler.toml 的 `services = [...]` inline 放在 `[vars]` 後 → binding 消失(2026-06-27issue #12
**錯誤模式**:以為「toml 裡有 `services = [{ binding="CYPHER_EXECUTOR", ... }]`」= binding 就會生效。
實際 self-hosted MCP 報 `CYPHER_EXECUTOR service binding not configured`binding 像憑空消失。
**根因(TOML 語法坑)**`services = [...]`**inline array 形式**,且位置在 `[vars]` table **之後**
TOML 規則:`[vars]` header 後的所有 key(含 `services`)都歸 `vars` table → `services` 被解析成
**`vars.services`(一個普通 env var 陣列)而非頂層 service bindings** → wrangler 看不到任何 binding。
self-hosted 部署 `injectMultiTenant``[vars]` 注入 `MULTI_TENANT` 後此問題暴露(官方不注入故沒早爆,
且 whoami 不需 binding)。
**正確做法**service binding 一律用 **`[[services]]` array-of-tables**(每個 binding 獨立頂層 table
不受 `[vars]` 影響),**不要用 inline `services = [...]`**——尤其檔案有 `[vars]` 時。對齊官方
`cypher-executor/wrangler.toml` 慣例(它用 `[[services]]` 故不中招)。
**驗法(決定性)**`wrangler deploy --dry-run --outdir /tmp/x` 列 bindings
- 正確 → `env.CYPHER_EXECUTOR (arcrun-cypher-executor) Worker`
- 中招 → `env.services ([{"binding":...}]) Environment Variable`binding 變成一個叫 services 的 env var)。
**通用教訓**TOML 頂層 key 必須在任何 `[table]` header 之前;要在 table 後宣告的集合用
array-of-tables`[[x]]`)不用 inline array。改 toml 後用 `wrangler --dry-run` 驗 binding 真的在
(別只看「檔裡有寫」)。連帶:這跟 mistakes #17「注入順序」、#11「死端點假綠」同類——**配置寫了 ≠ 生效**,要驗。日期:2026-06-27。
---
## 快速檢查清單(做新功能前)
- [ ] 這是工作流還是零件?問「有必要嗎?」
- [ ] API 有對應端點嗎?否 → 先補 API,不是在介面層拼裝
- [ ] 有 SDD 嗎?沒有 → 停手問 richblack
- [ ] 這段邏輯換介面(CLI → MCP)要重寫嗎?要 → 違反薄殼原則
- [ ] 會修改 KV/D1 的鑰匙嗎?是 → 檢查遷移冪等性 + 向後相容
- [ ] 需要人類同意嗎(暴露 / credential)?是 → 確保非 TTY 時拒絕
- [ ] 怎麼驗證完成?只靠輸出訊息 → 不夠,需 2xx + 數據
- [ ] 測試或初始化步驟有「失敗時怎辦」嗎?→ 不靠提醒,靠機制強制
- [ ] 碰 KBDB?確認打基本盤現存 route/entries /templates /records /entries/search),別假設 v3 /blocks /search 還在
- [ ] KBDB 要新可查欄位?用 json_extract 查 metadata_json,別加真欄(表不變鐵律,#18
- [ ] 改 deploy.ts toml 注入順序?想「後面還有沒有 pass(如 strip)會動同一段」(#17
- [ ] 新增/改薄殼工具?grep 確認它打的 server route 真的存在,別假綠(#19);宣稱對齊前跑 thin-shell-smoke.sh
- [ ] 想用 git log 查某檔歷史?先 git check-ignore——gitignored 檔無 git 史,時間靠檔內註記(#20
- [ ] 改 wrangler.toml service binding?用 [[services]] 不用 inline services=[...][vars] 後會被吸走);改完 wrangler --dry-run 驗 binding 真在(#21