Claude Code 的 Compact 壓縮實際上是如何運作的
首先,Claude Code 的 compact 壓縮會透過兩個路徑執行:
- 手動 compact(/compact):由使用者明確觸發。可以在任何時間點執行。
- 自動 compact:當會話上下文使用量接近上限時自動執行(實際上大約在 90-95%)。它並不會在使用者預期的確切時刻執行,而是在你突然發現它已被壓縮的對話輪次邊界上執行。
在這兩個路徑中,內容是一樣的:將先前的對話歷史發送給 LLM 以建立自然語言摘要,然後將摘要 + 最近的對話輪次 + 系統提示重組為新的上下文。摘要會變成一個敘述性的「完成了什麼工作」的描述,結果是「為什麼做出那個選擇」、「哪些計畫被拒絕了」以及「我們目前處於哪個階段」等決策結構會被弱化。此外,一旦壓縮執行,就是一個不可逆的操作;你無法回到壓縮前的原始日誌。
從鉤子的角度來看,壓縮可以透過 PostCompact 事件來捕獲。但是,PostCompact 鉤子無法回傳 additionalContext,因此沒有辦法在壓縮後立即直接向 Agent 注入指令。要插入指令,必須透過後續的 UserPromptSubmit 事件。這個限制決定了本文中的鉤子設計。
標準 Compact 壓縮的問題在哪裡
Claude Code 的 /compact 會總結對話歷史,並將結果作為新會話的初始上下文。雖然摘要本身是合理的,但它是一個「過去的工作日誌」,而不是「接下來要做什麼的指令」。因此,壓縮後的 Agent 容易出現以下誤解:
- 失去「工作指令」和「工作日誌」之間的界線。如果摘要說「考慮過方案 A」,Agent 可能會開始實施方案 A,而不是實際採用的方案 B。
- 混淆驗證階段與實施階段。「在部署前先在實際機器上驗證」的前提從摘要中遺失,Agent 可能會在下一輪對話中就跳入破壞性的部署操作。
- 再次提出已被否決的錯誤方法。摘要可能記錄了某個方法被「嘗試過」,但沒有記錄「被拒絕的原因」。
- 失去「會話狀態」,例如計劃模式、worker 委派或任務樹。Agent 可能在退出計劃模式後繼續工作,或者忘記在 tmux 中運行的 worker 的存在,並開始自己動手做工作。
實際發生了哪些意外
回顧我過去一週的會話,我在 compact 壓縮後觀察到至少四種模式。這些似乎都源於未能將「工作指令」與「工作日誌」區分開來:
- 部署前未驗證:儘管同意「在放置檔案前先在機器上驗證行為」,但 Agent 在壓縮後只看到了摘要中的完成影像,跳過了驗證步驟,直接覆蓋了目標位置。
- 重新執行已被否決的捷徑:因為摘要將一個失敗的捷徑方法記錄為「嘗試過的步驟」,Agent 重新執行了它,再次陷入同樣的失敗。
- 忘記設計原則:在會話期間建立的原則,例如「編輯原始程式碼儲存庫而非目標位置」,從摘要中遺失,導致 Agent 進行了短期有效但不可重現的直接編輯。
- 誤解任務目標:壓縮前修復的測試的具體目的遺失了,導致 Agent 偏離到周邊調查,並開始朝著完全不同方向的工作。
這些情況在同一個會話中多次發生,每次 compact 壓縮後都會出現不同類型的誤解。我認為這些是標準 compact 壓縮規格的結構性缺陷,而不是孤立的意外。
我建立的解決方案
(1) compact-prep 技能
一個以斜線命令實現的技能,使用者在輸入 /compact 之前明確執行。它會提取可能無法納入摘要的「決策結構」和「會話狀態」,並以特定格式將它們儲存到固定路徑(${TMPDIR}/claude-compact-state/<session_id>.md)的狀態檔案中。
儲存的項目包括那些難以在摘要中捕捉的內容:
- 活躍計劃(計劃檔案路徑和當前階段)
- 任務清單摘要(進行中的任務和補充說明)
- 會話決策(採用的計劃 / 拒絕的計劃 / 拒絕原因)
- 限制條件與障礙
- Worker 拓撲結構(tmux-bridge 面板 / 角色 / 指派)
- 編輯中的檔案(未儲存或未驗證變更的備註)
- 復原備註(留給壓縮後的自己的一封信)
關鍵設計點是「機械強制執行」。它使用硬性關卡來防止在找不到 session_id 時建立檔案,使用固定標題的強制函數來檢測遺漏,並限制允許的工具以防止狀態損毀。
(2) 兩階段鉤子:PostCompact + UserPromptSubmit
即使 (1) 寫入了狀態檔案,如果壓縮後的 Agent 不讀取它,那也是毫無意義的。由於 PostCompact 無法回傳 additionalContext,我使用了兩階段的配置:
- PostCompact 鉤子會寫入一個帶有 session_id 的標記檔案(不包含任何指令,僅記錄壓縮已發生)。
- 當下一個 UserPromptSubmit 鉤子檢測到該標記時,它會透過 additionalContext 注入指令:「讀取計劃檔案 / 讀取狀態檔案 / 檢查任務清單 / 將摘要的下一步視為假設 / 如果計劃模式被停用則確認重新進入」,然後刪除該標記。
由於 Claude Code 在鉤子之間缺乏共享狀態機制,所以檔案系統上的標記是唯一的通訊路徑。這使得每個鉤子保持精簡,具有單一職責。在正常的對話輪次中,開銷僅為一次 test -f 檢查。
(3) 60% 通知(避免自動 compact)
前述工具假設使用者手動執行 /compact-prep → /compact。然而,自動 compact 會在沒有警告的情況下執行,繞過了這個流程。如果自動 compact 先觸發,狀態檔案就不會被儲存,原始日誌會被壓縮成摘要,從而失去復原素材。
解決方案是建立一種狀態,讓使用者可以在自動 compact 觸發之前手動執行 prep。這就是 60% 通知的目的。
為什麼是 60%?
- 充足的提前時間:自動 compact 在大約 90-95% 時觸發。60% 提供了 30% 的餘裕,確保使用者在下一輪對話觸發自動 compact 之前看到警告。
- 有空間達到停頓點:在 60% 時,還有 30% 的容量可以完成一個子任務,然後再儲存狀態。
- 機械觸發:當你過於專注而沒有注意到上下文消耗時,它起到一個強制函數的作用。
- 1M 上下文假設:對於 200K 上下文,60% 太小了。我使用的是 1M 上下文(Opus 4.7),其中 60% 大約剩下 600K tokens——有足夠的空間完成工作並執行 prep。
實現方式包括一個 statusline 鉤子,在門檻值時寫入一個警告標記,以及一個 UserPromptSubmit 鉤子檢測到該標記並建議執行 /compact-prep。
結果
現在,會話即使在進行了約 10 次壓縮後仍然穩定,沒有邏輯崩潰!
- 因壓縮導致的工作損失幾乎為零。
- 被否決想法的重新提出已經消失。
- 計劃模式得以維持。
- 在 tmux 中運行多個 worker 時,不再忘記拓撲結構。
參考:實作程式碼
以下是實際的技能和鉤子程式碼。將它們複製到標準的 Claude Code 目錄(~/.claude/)即可使用。
A:compact-prep 技能主體
放置於 ~/.claude/skills/compact-prep/SKILL.md。
1---2name: compact-prep3description: |4 在 Claude Code 中執行 /compact 之前,將當前會話狀態儲存到臨時狀態檔案。5 強制觸發條件:/compact-prep、compact-prep、壓縮準備。6 請勿觸發:compact 後的復原、一般進度報告、計劃建立。7strict_procedure: true8argument-hint: "[復原備註]"9allowed-tools: Read Write Bash(~/.claude/scripts/get-session-id.sh *) Bash(mkdir *) Bash(date *) Bash(pwd)10---1112# compact-prep1314將會話中難以保留在壓縮摘要中的狀態儲存到15`${TMPDIR}/claude-compact-state/${SESSION_ID}.md` 中,以便在 `/compact` 前使用。1617## 嚴謹程序概覽1819- 嚴謹程度:嚴謹程序。20- 硬性關卡:如果無法取得 session_id 則停止。21- 強制函數:固定路徑和儲存後的強制標題檢查。22- 完成回執:報告檔案路徑、已儲存的項目,以及執行 `/compact` 的指引。2324## 程序25261. 透過 `~/.claude/scripts/get-session-id.sh` 取得 session_id。272. 設定路徑為 `${TMPDIR:-/tmp}/claude-compact-state/${SESSION_ID}.md`。283. 檢查任務清單、活躍計劃、tmux-bridge 和編輯中的檔案。294. 依序儲存標題:# Compact Prep State、## Active Plan、## Current Phase、## TaskList Summary、## Session Decisions、## Constraints and Blockers、## Worker Topology、## Editing Files、## Recovery Notes。305. 驗證所有標題皆已存在。316. 告知使用者:「準備完成。請執行 `/compact`。」
B:復原鉤子(2 階段)
B-1:PostCompact 鉤子
~/.claude/hooks/compaction-recovery.sh
B-2:UserPromptSubmit 鉤子(注入)
~/.claude/hooks/userpromptsubmit-compaction-recovery.sh
C:60% 通知
C-1:statusline 鉤子門檻值分支
新增至 ~/.claude/hooks/statusline.sh。
C-2:UserPromptSubmit 鉤子(提醒)
~/.claude/hooks/userpromptsubmit-compact-prep-reminder.sh
D:settings.json 註冊
將鉤子新增至 ~/.claude/settings.json。





