Config GC — Garbage Collection for Claude Code Setups
Borrowed from runtime garbage collection: periodically scan for objects that are no longer referenced, redundant, expired, or low-value, and reclaim the space. The critical difference: here, collection requires a human in the loop. Never delete autonomously.
When to Activate
- The user asks to clean up, audit, or slim down their Claude Code configuration
- The user complains about too many skills, noisy hooks, or slow session startup
- A monthly/periodic config review is due
- After installing a large skill pack (e.g. this repo), to reconcile overlaps with existing setup
Do NOT activate for: cleaning project source code (that's refactoring), clearing chat history, or uninstalling Claude Code itself.
Design Philosophy
- Append-only configs leak. Skills, memory files, hooks, and permission entries only ever get added. Without periodic review they rot silently.
- Regular audits beat one-time purges. Scan every ~30 days, propose a small batch of candidates each time.
- Per-channel strategies. Each accumulation type (skills, hooks, permissions, ...) has its own staleness signals — don't apply one rule everywhere.
- Soft-delete first. Rename to
.disabled > move to ~/.claude/_gc_trash/ > real deletion. Always keep an undo path.
- Forced human-in-the-loop. Every candidate gets its own
[y/n/skip] confirmation. No "yes to all" shortcut.
- Keep a log. Every GC run appends to
~/.claude/gc_log.md: what was touched, why, and how to undo it.
Scan Channels
| # |
Channel |
Path |
Staleness / redundancy signals |
| 1 |
Skills |
~/.claude/skills/*/ |
Heavily overlapping names; never triggered in recent transcripts; domain mismatch with the user's actual work; broken or empty SKILL.md |
| 2 |
Memory |
~/.claude/**/memory/*.md + its index |
Multiple index entries for one topic; contents contradicting newer entries; dates that have passed; orphan files missing from the index; sub-100-word fragments that should merge |
| 3 |
Hooks |
~/.claude/hooks/ + settings |
Scripts present on disk but referenced by no hook config; old versions superseded by rewrites |
| 4 |
Permissions |
permissions.allow in settings.json / settings.local.json |
Duplicate entries; specific entries already covered by a wildcard (e.g. Bash(git push) when Bash(*) is allowed); one-off grants from past experiments |
| 5 |
MCP servers |
~/.claude.json or project .mcp.json |
Servers that fail to connect; functional duplicates; long-unused |
| 6 |
Scheduled reminders / jobs |
wherever the user keeps them |
Fired one-shots older than 30 days; jobs whose target scripts no longer exist |
| 7 |
Project history |
~/.claude/projects/*/ |
Stale handoff snapshots; session records superseded by newer state |
| 8 |
Runtime caches |
cache/, file-history/, logs/, shell-snapshots/ |
Sort by size and mtime; propose items >30 days old and large |
Workflow
- Scan all channels (or the subset the user names). Collect candidates with: path, channel, signal that flagged it, size, last-modified.
- Rank by confidence (broken/orphaned = high; merely old = low) and present as a numbered table. Cap each run at ~20 candidates — GC is periodic, not exhaustive.
- Confirm one by one. For each candidate show the evidence, then ask
[y/n/skip]. The user can stop at any point.
- Soft-delete confirmed items: prefer
.disabled rename for skills/hooks and _gc_trash/<date>/ move for files. Permission entries live in JSON (no comments possible): back up the settings file, record each removed entry verbatim in gc_log.md, then remove it from the allow array with jq. Only hard-delete when the user explicitly asks.
- Log the run to
~/.claude/gc_log.md: timestamp, items actioned, undo instructions.
- Report: reclaimed size, channels still healthy, suggested next review date.
Example Scan Commands
Orphaned hook scripts (channel 3) — scripts on disk that no hook config references:
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
Redundant permission entries (channel 4) — duplicates, and specific grants shadowed by a wildcard:
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
Largest stale caches (channel 8) — du -k instead of GNU-only find -printf, so it works on macOS/BSD too:
find ~/.claude/file-history ~/.claude/shell-snapshots -type f -mtime +30 \
-exec du -k {} + 2>/dev/null | sort -rn | head -20
Soft-delete with undo path (capture the date once so the log can't disagree with the directory):
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
Removing a confirmed-redundant permission entry (JSON has no comments — back up, log, then edit):
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
Anti-Patterns
- Bulk approval. Asking "delete all 15? [y/n]" defeats the design. One item, one decision.
- Hard-deleting on first pass. If there's no
_gc_trash/ copy or .disabled rename, you did it wrong.
- Treating "old" as "dead". A skill untouched for 60 days may be seasonal (tax season, quarterly reviews). Age is a signal, not a verdict — that's why a human confirms.
- Cleaning memory by truncation. Merging two contradicting memory files requires reading both and keeping the newer truth, not deleting the longer one.
- Touching anything outside
~/.claude (or the project's .claude/). Config GC never wanders into source trees.
Best Practices
- Run after big additions, not just on a calendar: installing a 50-skill pack is exactly when overlap with existing skills appears.
- When two skills overlap, prefer disabling the one with the weaker trigger description — it's the one that was probably never firing anyway.
- Permission cleanup is the highest-value channel per minute spent: redundant allow-entries make security review harder.
- Keep
gc_log.md forever. It's tiny, and "when did I disable that hook and why" comes up more often than you'd think.
Related Skills
skill-stocktake — audits skill quality; config-gc audits skill existence. Run stocktake on what survives GC.
workspace-surface-audit — the additive counterpart: recommends what to install. config-gc is the subtractive half of the same lifecycle.
configure-ecc — after installing skills with it, run config-gc to reconcile overlaps with your pre-existing setup.
continuous-learning — produces the memory files this skill later audits.
security-review — pairs well with the permissions channel.
Source: affaan-m/ECC → skills/config-gc/SKILL.md
1---2name: config-gc3description: Garbage collection for your Claude Code configuration. Periodically scans ~/.claude (skills, memory, hooks, permissions, MCP servers, caches) for redundant, stale, orphaned, or low-value items, then walks the user through a confirm-each-deletion cleanup. Use when the user says "clean up my config", "config GC", "too many skills", "audit my setup", "my .claude is bloated", or asks for a periodic config review.4---5# Config GC — Garbage Collection for Claude Code Setups
6
7Borrowed from runtime garbage collection: periodically scan for objects that are no longer referenced, redundant, expired, or low-value, and reclaim the space. The critical difference: **here, collection requires a human in the loop. Never delete autonomously.**
8
9## When to Activate
10
11- The user asks to clean up, audit, or slim down their Claude Code configuration
12- The user complains about too many skills, noisy hooks, or slow session startup
13- A monthly/periodic config review is due
14- After installing a large skill pack (e.g. this repo), to reconcile overlaps with existing setup
15
16Do NOT activate for: cleaning project source code (that's refactoring), clearing chat history, or uninstalling Claude Code itself.
17
18## Design Philosophy
19
201. **Append-only configs leak.** Skills, memory files, hooks, and permission entries only ever get added. Without periodic review they rot silently.
212. **Regular audits beat one-time purges.** Scan every ~30 days, propose a small batch of candidates each time.
223. **Per-channel strategies.** Each accumulation type (skills, hooks, permissions, ...) has its own staleness signals — don't apply one rule everywhere.
234. **Soft-delete first.** Rename to `.disabled` > move to `~/.claude/_gc_trash/` > real deletion. Always keep an undo path.
245. **Forced human-in-the-loop.** Every candidate gets its own `[y/n/skip]` confirmation. No "yes to all" shortcut.
256. **Keep a log.** Every GC run appends to `~/.claude/gc_log.md`: what was touched, why, and how to undo it.
26
27## Scan Channels
28
29| # | Channel | Path | Staleness / redundancy signals |
30|---|---------|------|--------------------------------|
31| 1 | Skills | `~/.claude/skills/*/` | Heavily overlapping names; never triggered in recent transcripts; domain mismatch with the user's actual work; broken or empty SKILL.md |
32| 2 | Memory | `~/.claude/**/memory/*.md` + its index | Multiple index entries for one topic; contents contradicting newer entries; dates that have passed; orphan files missing from the index; sub-100-word fragments that should merge |
33| 3 | Hooks | `~/.claude/hooks/` + settings | Scripts present on disk but referenced by no hook config; old versions superseded by rewrites |
34| 4 | Permissions | `permissions.allow` in `settings.json` / `settings.local.json` | Duplicate entries; specific entries already covered by a wildcard (e.g. `Bash(git push)` when `Bash(*)` is allowed); one-off grants from past experiments |
35| 5 | MCP servers | `~/.claude.json` or project `.mcp.json` | Servers that fail to connect; functional duplicates; long-unused |
36| 6 | Scheduled reminders / jobs | wherever the user keeps them | Fired one-shots older than 30 days; jobs whose target scripts no longer exist |
37| 7 | Project history | `~/.claude/projects/*/` | Stale handoff snapshots; session records superseded by newer state |
38| 8 | Runtime caches | `cache/`, `file-history/`, `logs/`, `shell-snapshots/` | Sort by size and mtime; propose items >30 days old and large |
39
40## Workflow
41
421. **Scan** all channels (or the subset the user names). Collect candidates with: path, channel, signal that flagged it, size, last-modified.
432. **Rank** by confidence (broken/orphaned = high; merely old = low) and present as a numbered table. Cap each run at ~20 candidates — GC is periodic, not exhaustive.
443. **Confirm one by one.** For each candidate show the evidence, then ask `[y/n/skip]`. The user can stop at any point.
454. **Soft-delete confirmed items**: prefer `.disabled` rename for skills/hooks and `_gc_trash/<date>/` move for files. Permission entries live in JSON (no comments possible): back up the settings file, record each removed entry verbatim in `gc_log.md`, then remove it from the `allow` array with `jq`. Only hard-delete when the user explicitly asks.
465. **Log** the run to `~/.claude/gc_log.md`: timestamp, items actioned, undo instructions.
476. **Report**: reclaimed size, channels still healthy, suggested next review date.
48
49## Example Scan Commands
50
51Orphaned hook scripts (channel 3) — scripts on disk that no hook config references:
52
53```bash
54for f in ~/.claude/hooks/*; do
55 name=$(basename "$f")
56 grep -rq "$name" ~/.claude/settings.json ~/.claude/settings.local.json 2>/dev/null \
57 || echo "ORPHAN: $f"
58done
59```
60
61Redundant permission entries (channel 4) — duplicates, and specific grants shadowed by a wildcard:
62
63```bash
64jq -r '.permissions.allow[]' ~/.claude/settings.local.json | sort | uniq -d
65if jq -e '.permissions.allow | index("Bash(*)")' ~/.claude/settings.local.json >/dev/null; then
66 jq -r '.permissions.allow[]' ~/.claude/settings.local.json \
67 | grep '^Bash(' | grep -vF 'Bash(*)'
68fi
69```
70
71Largest stale caches (channel 8) — `du -k` instead of GNU-only `find -printf`, so it works on macOS/BSD too:
72
73```bash
74find ~/.claude/file-history ~/.claude/shell-snapshots -type f -mtime +30 \
75 -exec du -k {} + 2>/dev/null | sort -rn | head -20
76```
77
78Soft-delete with undo path (capture the date once so the log can't disagree with the directory):
79
80```bash
81gc_date=$(date +%Y-%m-%d)
82mkdir -p ~/.claude/_gc_trash/$gc_date
83mv ~/.claude/skills/dead-skill ~/.claude/_gc_trash/$gc_date/
84echo "$(date -Iseconds) moved skills/dead-skill -> _gc_trash/$gc_date/ (undo: mv back)" >> ~/.claude/gc_log.md
85```
86
87Removing a confirmed-redundant permission entry (JSON has no comments — back up, log, then edit):
88
89```bash
90cp ~/.claude/settings.local.json ~/.claude/settings.local.json.bak
91echo "$(date -Iseconds) removed permission entry: Bash(git push) (undo: restore from .bak or re-add)" >> ~/.claude/gc_log.md
92jq '.permissions.allow -= ["Bash(git push)"]' ~/.claude/settings.local.json.bak \
93 > ~/.claude/settings.local.json
94```
95
96## Anti-Patterns
97
98- **Bulk approval.** Asking "delete all 15? [y/n]" defeats the design. One item, one decision.
99- **Hard-deleting on first pass.** If there's no `_gc_trash/` copy or `.disabled` rename, you did it wrong.
100- **Treating "old" as "dead".** A skill untouched for 60 days may be seasonal (tax season, quarterly reviews). Age is a signal, not a verdict — that's why a human confirms.
101- **Cleaning memory by truncation.** Merging two contradicting memory files requires reading both and keeping the newer truth, not deleting the longer one.
102- **Touching anything outside `~/.claude`** (or the project's `.claude/`). Config GC never wanders into source trees.
103
104## Best Practices
105
106- Run after big additions, not just on a calendar: installing a 50-skill pack is exactly when overlap with existing skills appears.
107- When two skills overlap, prefer disabling the one with the weaker trigger description — it's the one that was probably never firing anyway.
108- Permission cleanup is the highest-value channel per minute spent: redundant allow-entries make security review harder.
109- Keep `gc_log.md` forever. It's tiny, and "when did I disable that hook and why" comes up more often than you'd think.
110
111## Related Skills
112
113- `skill-stocktake` — audits skill *quality*; config-gc audits skill *existence*. Run stocktake on what survives GC.
114- `workspace-surface-audit` — the additive counterpart: recommends what to install. config-gc is the subtractive half of the same lifecycle.
115- `configure-ecc` — after installing skills with it, run config-gc to reconcile overlaps with your pre-existing setup.
116- `continuous-learning` — produces the memory files this skill later audits.
117- `security-review` — pairs well with the permissions channel.
118
119---
120
121**Source:** [`affaan-m/ECC`](https://github.com/affaan-m/ECC) → `skills/config-gc/SKILL.md`