Skip to content

Instantly share code, notes, and snippets.

@paralin
Last active February 10, 2026 20:59
Show Gist options
  • Select an option

  • Save paralin/20f065be8b25c9f334dcd225c223d959 to your computer and use it in GitHub Desktop.

Select an option

Save paralin/20f065be8b25c9f334dcd225c223d959 to your computer and use it in GitHub Desktop.
Claude Code ~/.claude/ cleanup script and file map
#!/usr/bin/env bash
#
# cleanup.bash - Clean up temporary/ephemeral files from ~/.claude/
#
# Safe to run at any time. Skips files from currently active sessions.
# Does NOT delete: CLAUDE.md, settings.json, skills/, plugins/, history.jsonl,
# session transcripts (*.jsonl), session indexes, teams/, etc.
#
# Usage:
# bash cleanup.bash # dry run (default)
# bash cleanup.bash --dry-run
# bash cleanup.bash --run # actually delete files
#
set -euo pipefail
CLAUDE_DIR="${HOME}/.claude"
DRY_RUN=true
TOTAL_FREED=0
if [[ "${1:-}" == "--run" ]]; then
DRY_RUN=false
elif [[ "${1:-}" != "--dry-run" && -n "${1:-}" ]]; then
echo "Usage: $0 [--dry-run | --run]"
echo " --dry-run Show what would be deleted (default)"
echo " --run Actually delete files"
exit 1
fi
if ! [[ -d "$CLAUDE_DIR" ]]; then
echo "Error: $CLAUDE_DIR does not exist"
exit 1
fi
# Collect active session IDs by checking which files are currently open
ACTIVE_SESSIONS=()
# Use lsof to find open files in the claude dir (works on macOS and Linux)
if command -v lsof &>/dev/null; then
while IFS= read -r uuid; do
[[ -n "$uuid" ]] && ACTIVE_SESSIONS+=("$uuid")
done < <(lsof +D "$CLAUDE_DIR/debug" 2>/dev/null \
| awk '{print $NF}' \
| grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' \
| sort -u || true)
fi
# Also treat very recently modified debug logs as active (within last 5 min)
while IFS= read -r f; do
[[ -e "$f" ]] || continue
uuid=$(basename "$f" .txt)
ACTIVE_SESSIONS+=("$uuid")
done < <(find "$CLAUDE_DIR/debug" -name "*.txt" -mmin -5 2>/dev/null || true)
# Deduplicate
if [[ ${#ACTIVE_SESSIONS[@]} -gt 0 ]]; then
readarray -t ACTIVE_SESSIONS < <(printf '%s\n' "${ACTIVE_SESSIONS[@]}" | sort -u)
fi
is_active_session() {
local check_id="$1"
for active_id in "${ACTIVE_SESSIONS[@]+"${ACTIVE_SESSIONS[@]}"}"; do
if [[ "$check_id" == "$active_id" ]]; then
return 0
fi
done
return 1
}
# Get file/dir size in bytes (portable)
get_size() {
if [[ -e "$1" ]]; then
if [[ -d "$1" ]]; then
du -sk "$1" 2>/dev/null | awk '{print $1 * 1024}'
else
stat -f%z "$1" 2>/dev/null || stat --format=%s "$1" 2>/dev/null || echo 0
fi
else
echo 0
fi
}
human_size() {
local bytes=$1
if (( bytes >= 1073741824 )); then
printf "%.1f GB" "$(echo "scale=1; $bytes / 1073741824" | bc)"
elif (( bytes >= 1048576 )); then
printf "%.1f MB" "$(echo "scale=1; $bytes / 1048576" | bc)"
elif (( bytes >= 1024 )); then
printf "%.1f KB" "$(echo "scale=1; $bytes / 1024" | bc)"
else
printf "%d B" "$bytes"
fi
}
remove_item() {
local path="$1"
local desc="$2"
local size
size=$(get_size "$path")
TOTAL_FREED=$((TOTAL_FREED + size))
if $DRY_RUN; then
echo " [DRY RUN] Would delete: $path ($(human_size "$size")) - $desc"
else
rm -rf "$path"
echo " Deleted: $path ($(human_size "$size")) - $desc"
fi
}
echo "=== Claude Code Cleanup ==="
echo "Mode: $( $DRY_RUN && echo 'DRY RUN (use --run to actually delete)' || echo 'LIVE - deleting files' )"
echo "Active sessions detected: ${#ACTIVE_SESSIONS[@]}"
if [[ ${#ACTIVE_SESSIONS[@]} -gt 0 ]]; then
echo " Skipping files for: ${ACTIVE_SESSIONS[*]}"
fi
echo ""
# --- 1. Security warning state files ---
echo "--- Security warning state files ---"
for f in "$CLAUDE_DIR"/security_warnings_state_*.json; do
[[ -e "$f" ]] || continue
uuid=$(basename "$f" | sed 's/security_warnings_state_//;s/\.json//')
if is_active_session "$uuid"; then
echo " [SKIP] $f (active session)"
continue
fi
remove_item "$f" "session security warnings"
done
# --- 2. Debug logs ---
echo ""
echo "--- Debug logs ---"
count=0
for f in "$CLAUDE_DIR"/debug/*.txt; do
[[ -e "$f" ]] || continue
uuid=$(basename "$f" .txt)
if is_active_session "$uuid"; then
echo " [SKIP] $f (active session)"
continue
fi
remove_item "$f" "debug log"
count=$((count + 1))
done
echo " ($count debug log files)"
# --- 3. Shell snapshots ---
echo ""
echo "--- Shell snapshots ---"
count=0
for f in "$CLAUDE_DIR"/shell-snapshots/snapshot-*.sh; do
[[ -e "$f" ]] || continue
remove_item "$f" "shell snapshot"
count=$((count + 1))
done
echo " ($count shell snapshot files)"
# --- 4. Paste cache ---
echo ""
echo "--- Paste cache ---"
count=0
for f in "$CLAUDE_DIR"/paste-cache/*.txt; do
[[ -e "$f" ]] || continue
remove_item "$f" "paste cache"
count=$((count + 1))
done
echo " ($count paste cache files)"
# --- 5. Empty session-env directories ---
echo ""
echo "--- Session env directories ---"
count=0
for d in "$CLAUDE_DIR"/session-env/*/; do
[[ -d "$d" ]] || continue
uuid=$(basename "$d")
if is_active_session "$uuid"; then
echo " [SKIP] $d (active session)"
continue
fi
remove_item "$d" "session env dir"
count=$((count + 1))
done
echo " ($count session-env directories)"
# --- 6. Todo lists ---
echo ""
echo "--- Todo lists ---"
count=0
for f in "$CLAUDE_DIR"/todos/*.json; do
[[ -e "$f" ]] || continue
uuid=$(echo "$(basename "$f")" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1)
if [[ -n "$uuid" ]] && is_active_session "$uuid"; then
echo " [SKIP] $f (active session)"
continue
fi
remove_item "$f" "todo list"
count=$((count + 1))
done
echo " ($count todo files)"
# --- 7. Task tracking ---
echo ""
echo "--- Task tracking ---"
count=0
for d in "$CLAUDE_DIR"/tasks/*/; do
[[ -d "$d" ]] || continue
uuid=$(basename "$d")
if is_active_session "$uuid"; then
echo " [SKIP] $d (active session)"
continue
fi
remove_item "$d" "task data"
count=$((count + 1))
done
echo " ($count task directories)"
# --- 8. Project subagent logs and tool-results ---
echo ""
echo "--- Project subagent logs & tool results ---"
sa_count=0
tr_count=0
for project_dir in "$CLAUDE_DIR"/projects/*/; do
[[ -d "$project_dir" ]] || continue
for session_dir in "$project_dir"/*/; do
[[ -d "$session_dir" ]] || continue
# Skip if it's not a UUID directory (e.g., sessions-index.json)
uuid=$(basename "$session_dir")
echo "$uuid" | grep -qE '^[0-9a-f]{8}-' || continue
if is_active_session "$uuid"; then
echo " [SKIP] $session_dir (active session)"
continue
fi
if [[ -d "$session_dir/subagents" ]]; then
for f in "$session_dir"/subagents/*.jsonl; do
[[ -e "$f" ]] || continue
remove_item "$f" "subagent log"
sa_count=$((sa_count + 1))
done
fi
if [[ -d "$session_dir/tool-results" ]]; then
for f in "$session_dir"/tool-results/*.txt; do
[[ -e "$f" ]] || continue
remove_item "$f" "tool result cache"
tr_count=$((tr_count + 1))
done
fi
done
done
echo " ($sa_count subagent logs, $tr_count tool result files)"
# --- 9. Failed telemetry events ---
echo ""
echo "--- Failed telemetry events ---"
count=0
for f in "$CLAUDE_DIR"/telemetry/*.json; do
[[ -e "$f" ]] || continue
remove_item "$f" "failed telemetry"
count=$((count + 1))
done
echo " ($count telemetry files)"
# --- 10. Stats cache ---
echo ""
echo "--- Stats cache ---"
if [[ -f "$CLAUDE_DIR/stats-cache.json" ]]; then
remove_item "$CLAUDE_DIR/stats-cache.json" "stats cache"
fi
# --- 11. Orphaned plugin cache versions ---
echo ""
echo "--- Orphaned plugin cache versions ---"
count=0
while IFS= read -r orphan_marker; do
[[ -e "$orphan_marker" ]] || continue
ver_dir=$(dirname "$orphan_marker")
remove_item "$ver_dir" "orphaned plugin $(basename "$(dirname "$ver_dir")")/$(basename "$ver_dir")"
count=$((count + 1))
done < <(find "$CLAUDE_DIR/plugins/cache" -name ".orphaned_at" 2>/dev/null || true)
echo " ($count orphaned plugin versions)"
# --- Summary ---
echo ""
echo "=== Summary ==="
echo "Total space $( $DRY_RUN && echo 'that would be freed' || echo 'freed' ): $(human_size $TOTAL_FREED)"
echo ""
if $DRY_RUN; then
echo "Run with --run to actually delete these files."
fi

~/.claude/ File Map

Total size: ~2.7 GB

Config Files (KEEP)

Path Description
CLAUDE.md User's global instructions applied to all sessions
settings.json User settings: permissions, plugins, env vars
skills/ User-created skill definitions (hypercontext, interview, etc.)

Session Data

Path / Pattern Type Size Description
projects/<project>/<uuid>.jsonl Session transcripts ~996 MB Full conversation logs per project, keyed by session UUID
projects/<project>/<uuid>/subagents/ Subagent logs JSONL logs from spawned subagents within a session
projects/<project>/<uuid>/tool-results/ Tool output cache Cached tool call results (1182 files)
projects/<project>/sessions-index.json Session index small Index of sessions for a project
history.jsonl Command history ~2.3 MB Global session history / recent sessions list

Runtime / Ephemeral State

Path / Pattern Type Size Description
debug/<uuid>.txt Debug logs ~1.1 GB Per-session debug/trace logs (1250 files)
session-env/<uuid>/ Session env ~0 B Empty dirs capturing environment per session (424 dirs)
shell-snapshots/snapshot-*.sh Shell snapshots ~19 MB Shell environment snapshots (aliases, functions, env vars) (206 files)
paste-cache/<hash>.txt Paste cache ~1.8 MB Cached clipboard paste content (117 files)
security_warnings_state_<uuid>.json Warning state ~92 KB Per-session security warning acknowledgements (23 files)

Task / Todo Tracking

Path / Pattern Type Size Description
todos/<uuid>-agent-<uuid>.json Todo lists ~3.8 MB Per-session task/todo lists (970 files)
tasks/<uuid>/ Task data ~420 KB Structured task tracking per session (86 dirs)
plans/*.md Plans ~24 KB Saved implementation plans

Telemetry & Analytics

Path / Pattern Type Size Description
stats-cache.json Stats cache ~8 KB Cached usage statistics
statsig/ Feature flags ~36 KB Statsig feature flag evaluations and identifiers
telemetry/1p_failed_events.*.json Failed telemetry ~60 KB Failed first-party telemetry events queued for retry
usage-data/ Usage reports ~64 KB Usage facets and HTML reports

Plugins & Cache

Path / Pattern Type Size Description
plugins/cache/ Plugin cache ~7 MB Downloaded plugin packages (typescript-lsp, gopls-lsp, etc.)
plugins/installed_plugins.json Plugin state small List of installed plugins
plugins/known_marketplaces.json Marketplace list small Known plugin marketplaces
plugins/install-counts-cache.json Install counts small Plugin install count cache
cache/changelog.md Changelog cache small Cached changelog
plugins/cache/**/.orphaned_at Orphan markers tiny Marks old plugin versions superseded by newer ones

Teams

Path / Pattern Type Size Description
teams/ Team configs ~52 KB Team-related configuration

Glob Patterns for Safely Deletable Temporary Files

These patterns match ephemeral/temporary data that accumulates over time and can be safely removed when no sessions are active:

# Per-session security warning state (recreated per session)
security_warnings_state_*.json

# Debug/trace logs from past sessions
debug/*.txt

# Shell environment snapshots (recreated each session)
shell-snapshots/snapshot-*.sh

# Paste cache (clipboard content cache)
paste-cache/*.txt

# Empty session environment directories
session-env/*/

# Todo lists from past sessions
todos/*.json

# Task tracking from past sessions
tasks/*/

# Subagent logs within projects
projects/*/*/subagents/*.jsonl

# Tool result caches within projects
projects/*/*/tool-results/*.txt

# Failed telemetry events
telemetry/*.json

# Stats cache
stats-cache.json

# Plans (if no longer needed)
plans/*.md

# Orphaned plugin versions (old versions marked with .orphaned_at)
plugins/cache/**/.orphaned_at

Patterns to NEVER delete

CLAUDE.md              # User instructions
settings.json          # User settings & permissions
skills/                # User-created skills
plugins/               # Installed plugins and config
history.jsonl          # Session history
projects/*/sessions-index.json  # Session indexes
projects/*/*.jsonl     # Session transcripts (conversation history)
statsig/               # Feature flag state
teams/                 # Team configuration
cache/                 # App cache
usage-data/            # Usage reports
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment