跳轉到

Semi-Brain 系統迭代:68→94 分完整改進路徑與 Windows/WSL Hook 配置除錯

文檔資訊

  • 分類: debugging
  • 難度: advanced
  • 預估閱讀時間: 15 分鐘
  • 標籤: semi-brain, claude-code-hooks, windows-wsl, stop-hook, ledger, tags-schema, ci, bash-background-execution, slug-collision, frontmatter-validation, document-fusion, session-deduplication

摘要

記錄 semi-brain 知識管理系統從 68 分迭代到 94 分的完整改進過程,涵蓋 ledger 去重語義修正、tags/graph/stats 資料契約統一、slug collision 防護、frontmatter 驗證、CI 接測試、依賴拆分、retry-failed 指令,以及 Windows Claude Code Stop hook 與 WSL 環境的完整除錯實錄。新增關鍵發現:Stop hook 在進行中的 session 會多次觸發,導致 14 篇重複文檔,解法是同 session_id 觸發時改為融合更新而非跳過。

關鍵學習

  • Windows Claude Code 執行的是 Windows 原生 shell(cmd/PowerShell),hook command 不能呼叫 wsl.exe 或 Linux binary — 會報 cannot execute binary file

  • WSL 內的 Claude Code 有獨立的 ~/.claude/settings.json,Windows 端位於 %USERPROFILE%.claude\settings.json,兩者設定完全分開

  • Stop hook 在 session 進行中每次對話結束都會觸發,不是 session 完全關閉才觸發,同一個長 session 會產生多次觸發

  • 同 session 多次觸發時 hash 不同(因為對話內容在增長),ledger 去重機制擋不住 → 產生 14 篇重複文檔;解法:改為融合更新同 session_id 的已有文檔

  • bash background 執行(& 背景 / nohup)時,source 進來的函數不會被帶入 subshell,須避免依賴 sourced functions(如 ensure_spool_exists)

  • tee -a 搭配 >> 重導向會造成每行 log 重複兩次

  • retry-failed.sh 用 echo pipe 走 while 迴圈會產生 subshell,導致計數變數在外層讀不到;改用 here-string 解決

  • ledger 去重語義:failed 不應等同已處理,需允許 retry;processed 才是真正去重

  • tags.json 格式契約需統一,build-indexes / build-graph / generate-stats 三者讀取格式必須一致

  • 文檔生成後需順手觸發 indexes/graph/stats 更新,且子 subprocess 的 returncode 需明確記錄(non-fatal 但要可見)

技術細節

分數演進路徑

輪次 分數 主要改進
初始 68 有野心的原型,舊 API pipeline 混雜,ledger 語義錯誤
第一輪 83 移除舊 API 流程、修正 ledger 語義+lock、統一資料契約、文檔發布後自動更新 indexes/graph/stats
第二輪 89 補正式 tests(27 tests)、GitPublisher 記錄 subprocess returncode、挖出並修掉兩個真 bug、清理舊流程殘留
第三輪 91 CI 接測試、core/docs/extras 依賴拆分、retry-failed 指令、.gitignore 補齊
第四輪 92 修正 retry-failed subshell 計數問題(here-string)、CI 補安裝 requirements、測試 11/11 通過
第五輪 94 slug collision 防護、frontmatter 驗證、same-session dedupe 與真實模板對齊(session_id 進 frontmatter)、測試 27/27

Windows vs WSL Hook 環境差異(實際測試驗證)

Windows Claude Code 的 Stop hook 執行的是 Windows 原生 shell,不能使用: - /c/Windows/system32/wsl — 報 cannot execute binary file - /c/Windows/system32/wsl.exe — 同上 - /c/Windows/system32/cmd — 同上

正確做法:Windows 端設定用純 PowerShell/cmd 語法,WSL 端設定用 bash 語法,兩份設定完全獨立。

WSL 內設定位置:~/.claude/settings.json Windows 端設定位置:%USERPROFILE%\.claude\settings.json 或專案 .claude/settings.local.json

Stop Hook 觸發時機(重要釐清)

Stop hook 不是 session 完全關閉才觸發,而是**每次 Claude 停止輸出**(即每次問答結束)都觸發。這意味著: - 一個長 session 中,每次回答結束都會觸發一次 - 同一個 session 的 hash 每次都不同(因為對話內容在增長) - ledger 的 hash-based 去重無法防止同 session 多次觸發產生重複文檔 - 實際案例:一個 session 觸發了 14 次,產生 14 篇重複文檔

解法:改為「融合更新」模式 — 偵測到已有相同 session_id 的文檔時,合併新舊內容而非跳過或覆蓋。

Bash Background 執行限制

# 這樣會失敗:ensure_spool_exists 找不到
nohup bash -c 'source utils.sh && ensure_spool_exists && ...' &

# 正確做法:獨立 script,不依賴 source
nohup bash /path/to/process-and-publish.sh &

ensure_spool_exists: command not found 在 log 中屬於非阻塞性錯誤,不影響主流程,但應將所需函數抽成獨立 script。

Log 重複問題

# 錯誤:tee -a 加上 >> 雙重導向,每行寫兩次
some_command | tee -a logfile >> logfile

# 正確:只用一種
some_command >> logfile 2>&1
# 或
some_command 2>&1 | tee -a logfile

依賴分層架構

requirements.txt      → core: jinja2
requirements-docs.txt → docs: mkdocs, mkdocs-material, pyyaml  
requirements-extras.txt → memory/graph: mem0ai, sentence-transformers, graphiti-core

mkdocs 屬於部署/預覽層,不是核心 runtime,主處理鏈路(process-session-local.py)不需要它。

What Changed

核心系統層(68→92 分)

移除舊 API pipeline(GitHub Actions + Anthropic API 流程),統一為 local Claude Code CLI 主流程。修正 ledger 去重語義:failed 允許 retry,processed 才是真正去重,加 lock 防併發。統一 tags.json 資料契約({tag: {count, docs:[...]}}),讓 build-indexes / build-graph / generate-stats 三者一致。文檔發布後自動觸發 indexes/graph/stats 更新並記錄各子步驟 returncode(non-fatal 但可見)。補 27 個自動化測試並接進 CI。拆分 core/docs/extras 依賴層。新增 retry-failed 指令並修正 subshell 計數問題(echo pipe → here-string)。

品質控制層(92→94 分)

新增 slug collision 防護(同名文檔自動加 -2, -3 後綴)。將 session_id 放入 frontmatter,讓 _resolve_filepath() 的同 session 重複判斷在正式模板下成立。新增 frontmatter schema 驗證(目前為 warning,非 hard gate)。測試從 11 增加到 27 個。

同 session 融合更新(94 分後新增)

發現 Stop hook 在 session 進行中每次問答結束就觸發,同一個長 session 產生 14 篇重複文檔。舊的「已有文檔則跳過」策略過於粗暴。改為「融合更新」:偵測到相同 session_id 的已有文檔時,根據新舊對話內容進行有脈絡的合併更新,取代直接覆蓋。

So What

這些改進讓 semi-brain 從「有野心的原型」進化成「可持續運作的 v1 系統」。

Stop hook 自動觸發確保每次 Claude Code session 結束後都會嘗試處理並發布知識。但 Stop hook 觸發時機的誤解(以為是 session 關閉,實際是每次問答結束)導致了 14 篇重複文檔的問題,揭示了「session 去重」和「文檔內容去重」必須分層處理的重要性。

融合更新模式是讓知識庫真正有意義的關鍵 — 同一議題的多次討論應該讓知識累積而非互相覆蓋,也不應該因為「已有文檔」就粗暴地跳過更豐富的後續內容。

Windows/WSL 雙環境的除錯過程揭示了 Claude Code hook 跨環境設定的重要差異,是任何在 Windows + WSL 混合環境使用 Claude Code hook 的人必須了解的知識。

Trade-offs

  • Stop hook non-blocking 設計:以 nohup ... & 異步執行避免阻塞使用者,但錯誤不會立即可見,需依賴 log 觀察
  • 融合更新 vs 簡單跳過:融合更新保留知識演進脈絡,但需要 LLM 參與判斷,比跳過更耗資源;簡單跳過效率高但會遺失後續討論
  • frontmatter 驗證為 warning 非 hard gate:保守策略避免阻斷發布流程,但不能強制品質門檻
  • validate-docs --strict 排除 reports/、data/、stats.md:讓工具可執行(exit 0),但實際掃描文件數為 0,尚未成為有效的品質 gate
  • 依賴拆分為 core/docs/extras:降低安裝負擔,但需維護三份 requirements 檔案

Try It Fast

# 查看最新 hook log(WSL)
tail -f ~/.local/share/semi-brain/logs/semi-brain-hook.log

# 手動觸發 semi-brain pipeline
/projects/semi-brain/scripts/hooks/process-and-publish.sh

# 測試 retry-failed(修正 subshell 計數後)
bash /projects/semi-brain/scripts/retry-failed.sh

# 跑完整測試(27 tests)
python3 -m unittest discover -s tests -v

# 驗證 tags schema 契約
python3 -c "import json; d=json.load(open('.indexes/tags.json')); print(type(list(d.values())[0]))"
# 確認 frontmatter 驗證工具(注意:目前排除 reports/ 和 stats.md)
python3 scripts/validate-docs.py --strict

# 確認 Stop hook 在 WSL settings 中正確設定
cat ~/.claude/settings.json | python3 -m json.tool | grep -A5 '"Stop"'

Recommendation

Priority 1 — Stop Hook 正確理解與配置

  1. 分開設定兩個環境 — Windows 端用純 PowerShell/cmd 語法;WSL 端用 bash。兩者設定位置完全不同,修改一個不影響另一個
  2. 理解觸發時機 — Stop hook 在每次問答結束就觸發,一個長 session 會觸發多次;系統設計必須能處理同一 session 的多次觸發
  3. bash background 用獨立 script — 背景執行時永遠將需要的函數抽成獨立 script,不依賴 source

Priority 2 — 知識品質保護

  1. 融合更新取代跳過 — 同 session_id 的後續觸發應融合更新,而非粗暴跳過或覆蓋;融合應基於新舊對話的實際內容演進
  2. ledger 三態語義 — 嚴格區分 processed / failed / pendingfailed 允許 retry 但需搭配 lock

Priority 3 — 品質門檻提升(到 95 分的最後一步)

  1. Frontmatter hard gate — 將 frontmatter 驗證從 warning 提升為真正的品質門檻;同時確認 validate-docs --strict 的掃描範圍涵蓋實際知識文檔
  2. 運行驗證 — 連續跑 2-4 週,確認成功率高、無重複文檔、無卡死、無錯誤去重,才能接近 100 分

本文檔由 Semi-Brain 自動生成

Session ID: b0b2a293-9dc3-4b1b-a41f-7ab423a8fd45

分析信心度: 95%