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/<date>/。權限條目存在於 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 "ORPHAN: $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) moved skills/dead-skill -> _gc_trash/$gc_date/ (undo: mv back)" >> ~/.claude/gc_log.md
移除已確認冗餘的權限條目(JSON 沒有註解 —— 備份、記錄、再編輯):
cp ~/.claude/settings.local.json ~/.claude/settings.local.json.bak
echo "$(date -Iseconds) removed permission entry: Bash(git push) (undo: restore from .bak or re-add)" >> ~/.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— 與權限頻道搭配使用效果很好。