Config GC — Claude Code 設定的垃圾回收機制
借鑑運行時垃圾回收的概念:定期掃描不再被參照、冗餘、過期或低價值的物件,並回收空間。關鍵差異在於:此處的回收需要人為介入。絕不自主刪除。
何時啟用
- 使用者要求清理、審計或精簡其 Claude Code 設定
- 使用者抱怨技能太多、鉤子太雜或工作階段啟動緩慢
- 每月/定期設定審查時間到
- 安裝大型技能包(例如本倉庫)後,用以調和與現有設定的重疊
請勿在以下情況啟用:清理專案原始碼(那是重構)、清除聊天記錄或解除安裝 Claude Code 本身。
設計哲學
- 僅附加的設定會洩漏。 技能、記憶檔案、鉤子和權限條目只會不斷增加。沒有定期審查,它們會默默腐化。
- 定期審計優於一次性清理。 大約每 30 天掃描一次,每次提出少量候選項目。
- 各通道策略不同。 每種累積類型(技能、鉤子、權限……)都有其自己的過時訊號 — 不要對所有類型套用同一規則。
- 先軟刪除。 重新命名為
.disabled> 移至~/.claude/_gc_trash/> 真正刪除。始終保留還原路徑。 - 強制人機迴圈。 每個候選項目都有自己的
[y/n/skip]確認。沒有「全部同意」的捷徑。 - 保留日誌。 每次 GC 執行都會附加到
~/.claude/gc_log.md:記錄了什麼、原因以及如何還原。
掃描通道
| # | 通道 | 路徑 | 過時/冗餘訊號 |
|---|---|---|---|
| 1 | 技能 | ~/.claude/skills/*/ | 名稱高度重疊;近期對話記錄中從未被觸發;領域與使用者實際工作不符;SKILL.md 損壞或為空 |
| 2 | 記憶 | ~/.claude/**/memory/*.md 及其索引 | 同一主題有多個索引條目;內容與較新條目矛盾;日期已過期;索引中缺少孤立檔案;應合併的少於 100 字的片段 |
| 3 | 鉤子 | ~/.claude/hooks/ + 設定 | 存在於磁碟上但未被任何鉤子設定參照的腳本;已被重寫取代的舊版本 |
| 4 | 權限 | settings.json / settings.local.json 中的 permissions.allow | 重複條目;已包含在萬用字元中的特定條目(例如已允許 Bash(*) 時仍有 Bash(git push));過去實驗的一次性授權 |
| 5 | MCP 伺服器 | ~/.claude.json 或專案 .mcp.json | 無法連線的伺服器;功能重複的伺服器;長期未使用的伺服器 |
| 6 | 排程提醒/任務 | 使用者存放它們的任何位置 | 超過 30 天的已觸發一次性提醒;目標腳本已不存在的任務 |
| 7 | 專案歷史 | ~/.claude/projects/*/ | 過時的手動交接快照;已被較新狀態取代的工作階段記錄 |
| 8 | 運行時快取 | cache/、file-history/、logs/、shell-snapshots/ | 按大小和修改時間排序;建議超過 30 天且體積較大的項目 |
工作流程
- 掃描所有通道(或使用者指定的子集)。收集候選項目,包含:路徑、通道、觸發訊號、大小、最後修改時間。
- 依信心度排序(損壞/孤立 = 高;僅過時 = 低)並以編號表格呈現。每次執行最多約 20 個候選項目 — GC 是週期性的,並非窮舉。
- 逐一確認。 對每個候選項目顯示證據,然後詢問
[y/n/skip]。使用者可隨時停止。 - 軟刪除已確認項目:技能/鉤子優先使用
.disabled重新命名,檔案則優先移至_gc_trash/<日期>/。權限條目位於 JSON 中(無法加註解):先備份設定檔,將每個已移除的條目逐字記錄在gc_log.md中,然後使用jq從allow陣列中移除。僅在使用者明確要求時才永久刪除。 - 將執行記錄到
~/.claude/gc_log.md:時間戳、處理的項目、還原說明。 - 報告:回收的空間大小、仍健康的通道、建議的下次審查日期。
掃描指令範例
孤立的鉤子腳本(通道 3)— 存在於磁碟上但未被任何鉤子設定參照的腳本:
for f in ~/.claude/hooks/*; do
name=$(basename "$f")
grep -rq "$name" ~/.claude/settings.json ~/.claude/settings.local.json 2>/dev/null \
|| echo "孤立: $f"
done
冗餘的權限條目(通道 4)— 重複項目,以及被萬用字元遮蔽的特定授權:
jq -r '.permissions.allow[]' ~/.claude/settings.local.json | sort | uniq -d
if jq -e '.permissions.allow | index("Bash(*)")' ~/.claude/settings.local.json >/dev/null; then
jq -r '.permissions.allow[]' ~/.claude/settings.local.json \
| grep '^Bash(' | grep -vF 'Bash(*)'
fi
最大且過時的快取(通道 8)— 使用 du -k (而非僅 GNU 的 find -printf),以便在 macOS/BSD 上也能運作:
find ~/.claude/file-history ~/.claude/shell-snapshots -type f -mtime +30 \
-exec du -k {} + 2>/dev/null | sort -rn | head -20
軟刪除並保留還原路徑(一次記錄日期,避免日誌與目錄不一致):
gc_date=$(date +%Y-%m-%d)
mkdir -p ~/.claude/_gc_trash/$gc_date
mv ~/.claude/skills/dead-skill ~/.claude/_gc_trash/$gc_date/
echo "$(date -Iseconds) 已移動 skills/dead-skill -> _gc_trash/$gc_date/ (還原: mv 回來)" >> ~/.claude/gc_log.md
移除已確認冗餘的權限條目(JSON 無法加註解 — 先備份、記錄、再編輯):
cp ~/.claude/settings.local.json ~/.claude/settings.local.json.bak
echo "$(date -Iseconds) 已移除權限條目: Bash(git push) (還原: 從 .bak 還原或重新加入)" >> ~/.claude/gc_log.md
jq '.permissions.allow -= ["Bash(git push)"]' ~/.claude/settings.local.json.bak \
> ~/.claude/settings.local.json
反模式
- 批量批准。 詢問「刪除全部 15 個?[y/n]」違背了設計理念。一個項目,一個決定。
- 第一次就永久刪除。 如果沒有
_gc_trash/副本或.disabled重新命名,你就做錯了。 - 將「過時」等同於「死亡」。 一個技能 60 天未觸發可能是季節性的(稅季、季度審查)。年齡是訊號,不是判決 — 這就是為什麼需要人為確認。
- 透過截斷來清理記憶。 合併兩個矛盾的記憶檔案需要閱讀兩者並保留較新的事實,而不是刪除較長的那個。
- 碰觸
~/.claude(或專案的.claude/)以外的任何東西。 Config GC 絕不進入原始碼樹。
最佳實踐
- 在大量新增後執行,而不僅是按時程:安裝一個 50 技能的套件正是與現有技能產生重疊的時候。
- 當兩個技能重疊時,優先停用觸發描述較弱的那個 — 它很可能從未被觸發過。
- 權限清理是每分鐘花費最有價值的通道:冗餘的 allow 條目會使安全審查更加困難。
- 永久保留
gc_log.md。它很小,而且「我何時停用了那個鉤子以及為什麼」這個問題出現的頻率比你想像的更高。
相關技能
skill-stocktake— 審計技能品質;config-gc 審計技能是否存在。在 GC 留下的技能上執行 stocktake。workspace-surface-audit— 其互補對應:建議安裝什麼。config-gc 是同一個生命週期的減法部分。configure-ecc— 用它安裝技能後,執行 config-gc 以調和與現有設定的重疊。continuous-learning— 產生此技能稍後會審計的記憶檔案。security-review— 與權限通道搭配良好。