版本重點
claude-mem 從 v13.24.1 到 v13.24.23 是一段密集的修補列車——絕大多數版本在 2026-09-11 當天連續發布。
先說明一件事實:GitHub Release 涵蓋 v13.24.5 ~ v13.24.23,v13.24.2~v13.24.4 沒有建立對應的 Release(僅 npm 發佈)。
這一段沒有任何新的核心 memory API。它是一個穩定性與正確性區間,而且幾乎所有修正都繞著同一個主題打轉:
長壽背景服務的「身分」與「所有權」必須可驗證。
前一課談過 v13.24.1 修掉的 bundle 重建問題;這一課要說明那個問題為什麼會變成殺迴圈,以及後續二十個版本如何把同一類缺口補完。
核心案例:bundle 戳記錯誤造成的殺迴圈
這是這整段最重要的一個 bug。
發生什麼
v13.24.5 發佈時,committed plugin bundle 被錯誤地蓋成 13.24.1 的戳記。
於是執行時出現這個狀態:
manifest / package.json 說:13.24.5
實際 bundle 內容戳記說: 13.24.1
worker 偵測到 version mismatch → 認為自己過舊 → SIGKILL → respawn → 新的 worker 還是同一個錯戳記的 bundle → 再次 mismatch。
這就是殺迴圈 (kill loop):不斷重複回收風暴,而且每次重啟都得到同樣結果。
修法
v13.24.6 做了兩件事:
- worker-service 驗證自己的 bundle 版本,在 mismatch 時乾淨結束(而不是被殺掉後無限重生)。
- 以正確的
13.24.6戳記重建所有 bundle。
第一點才是關鍵。「乾淨結束」與「被 SIGKILL」在管理員眼中是兩件完全不同的事——前者是可觀察的失敗,後者會被誤判為當機而觸發重啟。
為什麼這值得學
這是一個自我指涉驗證的經典案例。系統需要能回答:
我(這個元件)是否真的是我被宣稱的那個版本?
如果元件無法檢查自身一致性,那麼唯一的檢測者是外部協調者,而外部協調者能做的只有「殺掉再試一次」——也就是這個迴圈。
同一個家族的問題在後續版本反覆出現:
| 版本 | 無法驗證的東西 | 後果 |
|---|---|---|
| v13.24.5/6 | bundle 版本戳記 | 殺迴圈 |
| v13.24.7 | Chroma writer owner(#3919)、無人擁有的 chroma-mcp 樹(#3905) | 鎖無法回收 / 殭屍行程樹 |
| v13.24.8 | 過期 origin_device_id(#3973) |
sync outbox 永久阻塞 |
| v13.24.17 | memory_session_id 重複註冊(#4027) |
sync outbox 放大 |
| v13.24.15 | daemon 繼承的舊 cwd |
寫入錯誤位置 |
主軸一:Chroma / 向量儲存的擁有權
Chroma 是 claude-mem 的向量儲存層,而它的行程與鎖生命週期是這段的第二大戰場。
| 修正 | 內容 |
|---|---|
| writer owner 重用 | 同一行程內重用 Chroma writer owner(#3919) |
| 無法讀取的 lock | 寬限期後回收(#3916) |
| backfill 失敗 | 連續批次失敗後停止,不無限重試 |
| chroma-mcp 樹 | 開機時回收沒有 worker 擁有的行程樹(#3905) |
| dead transport | handshake 失敗歸類為 ChromaUnavailableError(#3631) |
| JSON 解析崩潰 | 不再中止整個 sync(#4040) |
| 搜尋命中順序 | 依相關度而非日期 hydrate(#3881) |
「開機時回收沒有 worker 擁有的行程樹」與「寬限期後回收無法讀取的 lock」這兩個修正,本質上都在處理同一件事:孤兒資源。
孤兒資源的判定需要「所有權」這個概念。沒有它,你就只能選擇「永遠不回收」(洩漏)或「一律回收」(可能殺掉活著的東西)。兩者都錯。
主軸二:Worker 與 Supervisor 的併發
- parked slot-waiter 跟隨實際生效的併發上限(#3909)。注意「實際生效」這四個字——設定檔寫的不等於執行時跑的。
- provider 切換時重啟 parked generator(#3909)。
- health probe 遵循呼叫端剩餘的 deadline(#4026 / #3575)。給每個探測器一個固定逾時,在總預算有限時會累積超支。
- grammar 只建置一次,而非每次查詢重新編譯(#3926)。
- field optimizer 遵循 deadline(#3939)。
daemon 的 cwd 問題(#3727 / #3706)
daemon 是長壽行程,保留著它啟動時的 cwd。修正讓 daemon 不再繼承使用者的專案 cwd,worker cwd 固定為 DATA_DIR。
這修的是 Windows 上的資料夾鎖定問題。它與前一課 mempalace 的 mine --daemon 錯誤專案問題是完全相同的形狀:
相對路徑的意義取決於誰在解讀它。長壽行程的 cwd 是一個陷阱。
主軸三:Sync / Outbox 的邊界
雲端同步的 outbox 是一種「必須有界」的結構,否則單一壞項目就能卡住整條管線。
| 修正 | 內容 |
|---|---|
| 過期 device id | 被伺服器以 400 invalid_ops 拒絕的 ops 隔離,不再阻塞整個 outbox(#3973) |
| 未設定雲端同步 | 限制 sync_outbox 成長(#4031) |
memory_session_id |
只註冊一次並保留第一個 id(#4027) |
| NULL 寫入 | generator 啟動時不再寫 NULL memory_session_id(#3629) |
| 投影修復 | 排程的 projection repair 工作改為有界(#4046) |
**「隔離壞項目」與「重試」是不同的策略。**對於伺服器明確判定為無效(400)的項目,重試永遠不會成功,只會佔用管線。隔離才是正確處理。
主軸四:Hooks 與 Session 正確性
claude --resume也會觸發 SessionStart hook(#3969)——恢復的 session 一樣能拿到記憶脈絡。- 空字串
cwd視為缺值(#3977),而非解析到錯誤專案。這與 mempalace 的known_entities是同一個形狀:空值不該被當成有效值使用。 - 每 session 去重複 file-context 注入(#3486)。
- 不在 Claude Code hooks 上重建 login-shell PATH(#3453)。
- manifest 格式錯誤的 hook stdin 改為 fail open(#4006)——hook 壞掉不該讓整個 session 卡住。
- 略過 plugin cache session(#4042);project 名稱錨定到 Claude project 目錄(#4055)。
主軸五:Observer 的行為邊界
Observer 是「旁路記錄」元件,它的正確性標準與一般功能不同——它不該有副作用。
| 修正 | 內容 |
|---|---|
| prose-only observation | 保留而非丟棄(#3363) |
| 無文字的 assistant frame | 批次確認前略過(#3501) |
| init 回覆 | 不儲存從中解析出的 observation(#3877) |
| transport 失敗 | 重新排入佇列(#3998) |
| 反應式 quota 錯誤 | 暫停 session(#3999) |
| 圖片 payload | 從 observation prompt 移除(#3996) |
| session block | 配合 hook 輸出上限(#3995) |
「同步失敗要重新排隊」與「配額錯誤要暫停」是兩個必須區分的失敗類型:前者是暫時性、後者需要停止消耗。把它們當成同一種處理,就會一直對已知耗盡的配額發送註定失敗的請求。
主軸六:安全與憑證
shell-quote升級至 1.9.0,修補CVE-2026-13311(#4018)。- never send an empty messages array to the OpenRouter API(#3493)。
- macOS keychain account 正規化以符合 Claude Code(#4045 / #4037)——keychain 項目名稱不一致會讓憑證「存在但找不到」。
- 支援
CLAUDE_CONFIG_DIR於 keychain 項目與 SDK 子行程環境(#3908)。 - Windows
CredReadshim 可編譯(#3726)——OAuth token 查詢在 Windows 上才真正可用。 - 改為發佈 runtime-only 的
package.json,不再把開發用的 tree-sitter grammars 出貨到 marketplace(#3640)。
主軸七:模型與 Provider
- OpenRouter 改以精確主機名辨識,而非 URL 子字串比對(#3979)。子字串比對會讓相似網域被誤導向——這是一類很容易忽略的錯誤,因為它在正常情況下完全看不出來。
- 模型清單對應到原生
models[]fallback 陣列(#3971)。 - GPT-5 的 400 錯誤重試
max_completion_tokens(#4003)。 - 接受空的 OpenRouter reasoning 回應(#4017)。
- 可設定的 LLM 每次嘗試 deadline(#4000)。
升級建議
直接升到 v13.24.23。
特別提醒:若你停留在 v13.24.5,會遇到 bundle 戳記錯誤造成的殺迴圈,務必升級。
按使用情境對應的收益:
| 你的情境 | 相關修正 |
|---|---|
| 使用雲端同步 | outbox 隔離與成長上限(#3973、#4031) |
| 使用 Codex 或 Windsurf | hook trust、BOM 容忍(#4010、#3773) |
使用 claude --resume |
SessionStart hook 觸發(#3969) |
| Windows | daemon cwd、CredRead、Volta shim(#3727、#3726、#4008) |
| macOS | keychain 正規化(#4045) |
| 使用 OpenRouter | 精確主機名辨識、模型 fallback(#3979、#3971) |
學習重點
長壽背景服務的「身分」與「所有權」必須可驗證。
把這段 20 個版本的修正攤開,會看到它們處理的幾乎都是同一種缺口:
某個東西宣稱自己是 X
→ 系統無法驗證
→ 只能選擇相信或殺掉重試
→ 兩者都會出問題
具體的清單:
| 宣稱 | 驗證方式 | 沒有它會怎樣 |
|---|---|---|
| 「我是 13.24.5」 | bundle 戳記自檢 | 殺迴圈 |
| 「這把鎖是我的」 | writer owner 身分 | 鎖無法回收 |
| 「這個行程樹是我的」 | worker 擁有權 | 殭屍行程堆積 |
| 「這筆 op 仍有效」 | 伺服器回應(400 → 隔離) | outbox 永久阻塞 |
| 「這是我啟動時的 cwd」 | 絕對路徑 | 寫入錯誤位置 |
第二個可帶走的原則:「乾淨結束」與「被殺掉」是兩件不同的事。前者是可觀察、可診斷的失敗;後者會被誤判為當機並觸發重啟。讓元件能檢查自身一致性並在不符時主動結束,比讓外部協調者不斷重試要可靠得多。