AI > Claude Code >
一文详解如何使用脚本清理Claude Code缓存信息
脚本之家
我每天使用Claude Code大约一个月,注意到我的 ~/.claude 目录占了1.3GB。没有自动清理,会议数据不断堆积。
空间去哪儿了?
du -sh ~/.claude/*/ | sort -rh
| 目录 | 大小 | 存储内容 |
|---|---|---|
projects/ | 1.0 GB | 会话日志(UUID.jsonl + UUID目录) 会话日志(UUID.jsonl + UUID 目录) |
debug/ | 145 MB | 调试日志 |
shell-snapshots/ | 83 MB | Shell环境快照 |
file-history/ | 23 MB | 文件编辑历史(撤消) |
todos/ | 8.6 MB | 每会话TODO文件 |
plans/ | 1.3 MB | 计划模式输出 |
| 其他 | ~800 KB | 任务、粘贴缓存、图片缓存、安全警告状态_*.json |
最大的罪魁祸首是 projects/。每次会话创建一个 UUID.jsonl (完整对话日志)和一个 UUID/ 目录(子代理输出,计划文件)。这些用于 claude --resume <session-id> ,但你很少会重新启动超过一周的会话。
重要:不要碰 memory/
在每个项目目录中,有一个 memory/ 文件夹,包含 MEMORY.md ——这是Claude Code在会话间的持久记忆。删除它你将失去该项目的所有学习上下文。
脚本
我写了一个清理脚本,带有以下安全功能:
- 默认干运行——除非你传递
--execute,否则不会删除任何内容 - 双重保护——检查目录名称和UUID模式
- 可配置年龄——默认为7天,传递任何数字以更改
保存到 ~/.claude/scripts/cleanup-sessions.sh:
#!/bin/bash
set -euo pipefail
CLAUDE_DIR="$HOME/.claude"
PROJECTS_DIR="$CLAUDE_DIR/projects"
MAX_AGE_DAYS=7
DRY_RUN=true
numfmt_bytes() {
local bytes=$1
if [ "$bytes" -ge 1073741824 ]; then
printf "%.1f GB" "$(echo "$bytes / 1073741824" | bc -l)"
elif [ "$bytes" -ge 1048576 ]; then
printf "%.1f MB" "$(echo "$bytes / 1048576" | bc -l)"
elif [ "$bytes" -ge 1024 ]; then
printf "%.1f KB" "$(echo "$bytes / 1024" | bc -l)"
else
printf "%d B" "$bytes"
fi
}
cleanup_files() {
local dir="$1" pattern="$2" label="$3"
local count=0 bytes=0
[ -d "$dir" ] || return 0
while IFS= read -r -d '' file; do
local size
size=$(stat -f%z "$file" 2>/dev/null || echo 0)
bytes=$((bytes + size))
count=$((count + 1))
$DRY_RUN || rm -f "$file"
done < <(find "$dir" -maxdepth 1 -name "$pattern" -type f -mtime +"$MAX_AGE_DAYS" -print0)
if [ "$count" -gt 0 ]; then
echo " $label: ${count} 个文件 ($(numfmt_bytes "$bytes"))"
total_files=$((total_files + count))
total_bytes=$((total_bytes + bytes))
fi
}
cleanup_dir_contents() {
local dir="$1" label="$2"
local count=0 bytes=0
[ -d "$dir" ] || return 0
while IFS= read -r -d '' file; do
local size
size=$(stat -f%z "$file" 2>/dev/null || echo 0)
bytes=$((bytes + size))
count=$((count + 1))
$DRY_RUN || rm -f "$file"
done < <(find "$dir" -type f -mtime +"$MAX_AGE_DAYS" -print0)
if [ "$count" -gt 0 ]; then
echo " $label: ${count} 个文件 ($(numfmt_bytes "$bytes"))"
total_files=$((total_files + count))
total_bytes=$((total_bytes + bytes))
fi
}
for arg in "$@"; do
[[ "$arg" == "--execute" ]] && DRY_RUN=false
[[ "$arg" =~ ^[0-9]+$ ]] && MAX_AGE_DAYS="$arg"
done
$DRY_RUN && echo "=== 干运行(添加 --execute 以实际删除) ===" \
|| echo "=== 执行模式 ==="
echo "目标:超过${MAX_AGE_DAYS}天的文件"
echo ""
total_files=0
total_dirs=0
total_bytes=0
echo "[项目/会话日志]"
for project_dir in "$PROJECTS_DIR"/*/; do
[ -d "$project_dir" ] || continue
project_name=$(basename "$project_dir")
project_files=0 project_dirs=0 project_bytes=0
while IFS= read -r -d '' file; do
size=$(stat -f%z "$file" 2>/dev/null || echo 0)
project_bytes=$((project_bytes + size))
project_files=$((project_files + 1))
$DRY_RUN || rm -f "$file"
done < <(find "$project_dir" -maxdepth 1 -name "*.jsonl" -type f -mtime +"$MAX_AGE_DAYS" -print0)
while IFS= read -r -d '' dir; do
dirname=$(basename "$dir")
[[ "$dirname" == "memory" ]] && continue
if [[ "$dirname" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]; then
size=$(du -sk "$dir" 2>/dev/null | cut -f1)
project_bytes=$((project_bytes + size * 1024))
project_dirs=$((project_dirs + 1))
$DRY_RUN || rm -rf "$dir"
fi
done < <(find "$project_dir" -maxdepth 1 -type d -mtime +"$MAX_AGE_DAYS" -not -path "$project_dir" -print0)
if [ $((project_files + project_dirs)) -gt 0 ]; then
echo " $project_name: ${project_files} 个文件, ${project_dirs} 个目录 ($(numfmt_bytes $project_bytes))"
total_files=$((total_files + project_files))
total_dirs=$((total_dirs + project_dirs))
total_bytes=$((total_bytes + project_bytes))
fi
done
echo ""
echo "[其他临时数据]"
cleanup_dir_contents "$CLAUDE_DIR/debug" "调试/"
cleanup_dir_contents "$CLAUDE_DIR/shell-snapshots" "shell快照/"
cleanup_dir_contents "$CLAUDE_DIR/file-history" "文件历史/"
cleanup_dir_contents "$CLAUDE_DIR/todos" "待办事项/"
cleanup_dir_contents "$CLAUDE_DIR/plans" "计划/"
cleanup_dir_contents "$CLAUDE_DIR/tasks" "任务/"
cleanup_dir_contents "$CLAUDE_DIR/paste-cache" "粘贴缓存/"
cleanup_dir_contents "$CLAUDE_DIR/image-cache" "图片缓存/"
cleanup_files "$CLAUDE_DIR" "security_warnings_state_*.json" "安全警告状态"
echo ""
echo "--- 摘要 ---"
echo "文件: ${total_files}"
echo "目录: ${total_dirs} (UUID会话)"
echo "节省空间: $(numfmt_bytes $total_bytes)"
$DRY_RUN && [ $((total_files + total_dirs)) -gt 0 ] && echo "" && echo "要删除: $0 ${MAX_AGE_DAYS} --execute"用法
chmod +x ~/.claude/scripts/cleanup-sessions.sh # 干运行(默认,不删除任何东西) ~/.claude/scripts/cleanup-sessions.sh # 将年龄阈值更改为14天 ~/.claude/scripts/cleanup-sessions.sh 14 # 实际删除 ~/.claude/scripts/cleanup-sessions.sh 7 --execute
我在4周后的结果
文件: 6,806
目录: 246 (UUID会话)
节省空间: 1.3 GB
你绝对不应该清理的文件
| 路径 | 原因 |
|---|---|
projects/*/memory/ | 持久记忆(MEMORY.md) |
CLAUDE.md | 全局指令 |
settings.json | 用户设置 |
commands/ | 自定义斜杠命令 |
plugins/ | 已安装的插件 |
history.jsonl | 命令历史 |
Linux用户注意
这个脚本使用macOS stat -f%z。在Linux上,替换为 stat --format=%s 或使用 wc -c < "$file" 以实现跨平台兼容性。
知识扩展
清理 Claude Code 缓存信息脚本
#!/usr/bin/env bash
#
# clean-claude-cache.sh — Clean Claude Code cache files
#
# Usage:
# ./clean-claude-cache.sh # Dry-run (preview only, no deletion)
# ./clean-claude-cache.sh -f # Actually delete
# ./clean-claude-cache.sh -f --aggressive # Delete everything including sessions & history
# ./clean-claude-cache.sh -f --project-only # Only clean current project's .claude/
# ./clean-claude-cache.sh -f --global-only # Only clean ~/.claude/ (not project-level)
#
# Safety: Default mode is dry-run. Use -f to actually perform deletion.
#
set -euo pipefail
# ─── Colors ───────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m' # No Color
# ─── Defaults ─────────────────────────────────────────────────────────────────
FORCE=false
AGGRESSIVE=false
PROJECT_ONLY=false
GLOBAL_ONLY=false
VERBOSE=false
CLAude_DIR="$HOME/.claude"
TOTAL_FREED=0
# ─── Help ─────────────────────────────────────────────────────────────────────
usage() {
cat <<EOF
${BOLD}clean-claude-cache.sh${NC} — Clean Claude Code cache files
${BOLD}USAGE${NC}
$(basename "$0") [OPTIONS]
${BOLD}OPTIONS${NC}
-f, --force Actually delete files (default: dry-run)
-a, --aggressive Also clean sessions, history, and usage data
-p, --project-only Only clean project-level .claude/ directory
-g, --global-only Only clean global ~/.claude/ directory
-v, --verbose Show detailed file listing
-h, --help Show this help message
${BOLD}EXAMPLES${NC}
$(basename "$0") # Preview what would be cleaned
$(basename "$0") -f # Clean safe items only
$(basename "$0") -f --aggressive # Deep clean everything
$(basename "$0") -f --project-only # Clean only current project cache
${BOLD}SAFE CLEANUP${NC} (default -f mode)
• Cache files, debug logs, paste cache, downloads, stats cache
• File edit history, shell snapshots, session environments (>3 days old)
• Stale task files (>3 days), plan files (>7 days), backups (>3 days)
• Empty project directories (only sessions-index.json, no real data)
${BOLD}AGGRESSIVE CLEANUP${NC} (--aggressive, adds)
• Session metadata (>7 days old)
• Command history (history.jsonl — full delete)
• Usage data (>7 days old)
• Scheduled tasks
${BOLD}PRESERVED${NC} (never deleted)
• Settings (settings.json, settings.local.json)
• Skills and agents
• Project memories (*/memory/)
• IDE configuration
• Harness rules
EOF
exit 0
}
# ─── Parse Arguments ──────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
-f|--force) FORCE=true; shift ;;
-a|--aggressive) AGGRESSIVE=true; shift ;;
-p|--project-only) PROJECT_ONLY=true; shift ;;
-g|--global-only) GLOBAL_ONLY=true; shift ;;
-v|--verbose) VERBOSE=true; shift ;;
-h|--help) usage ;;
*) echo -e "${RED}Unknown option: $1${NC}"; usage ;;
esac
done
if [[ "$PROJECT_ONLY" == true && "$GLOBAL_ONLY" == true ]]; then
echo -e "${RED}Error: --project-only and --global-only are mutually exclusive${NC}"
exit 1
fi
# ─── Helper Functions ─────────────────────────────────────────────────────────
# Calculate directory size in bytes
dir_size() {
local path="$1"
if [[ -e "$path" ]]; then
du -sk "$path" 2>/dev/null | cut -f1
else
echo 0
fi
}
# Format bytes to human-readable
human_size() {
local kb="$1"
if [[ "$kb" -ge 1048576 ]]; then
echo "$(echo "scale=1; $kb / 1048576" | bc)G"
elif [[ "$kb" -ge 1024 ]]; then
echo "$(echo "scale=1; $kb / 1024" | bc)M"
else
echo "${kb}K"
fi
}
# Clean a directory or file
clean_item() {
local path="$1"
local label="$2"
local category="$3" # "safe" or "aggressive"
# Skip if aggressive-only item but not in aggressive mode
if [[ "$category" == "aggressive" && "$AGGRESSIVE" != true ]]; then
return 0
fi
if [[ ! -e "$path" ]]; then
return 0
fi
local size_kb
size_kb=$(dir_size "$path")
local size_hr
size_hr=$(human_size "$size_kb")
if [[ "$FORCE" == true ]]; then
rm -rf "$path"
echo -e " ${GREEN}✓ Deleted${NC} ${DIM}${label}${NC} (${size_hr})"
else
echo -e " ${YELLOW}✗ Would delete${NC} ${DIM}${label}${NC} (${size_hr})"
fi
TOTAL_FREED=$((TOTAL_FREED + size_kb))
}
# Clean old files in a directory (older than N days)
clean_old_files() {
local dir="$1"
local label="$2"
local days="$3"
local category="$4"
if [[ "$category" == "aggressive" && "$AGGRESSIVE" != true ]]; then
return 0
fi
if [[ ! -d "$dir" ]]; then
return 0
fi
local count
count=$(find "$dir" -mindepth 1 -maxdepth 1 -mtime +"$days" 2>/dev/null | wc -l | tr -d ' ')
if [[ "$count" -eq 0 ]]; then
return 0
fi
local size_kb=0
while IFS= read -r f; do
local fsize
fsize=$(dir_size "$f")
size_kb=$((size_kb + fsize))
done < <(find "$dir" -mindepth 1 -maxdepth 1 -mtime +"$days" 2>/dev/null)
local size_hr
size_hr=$(human_size "$size_kb")
if [[ "$FORCE" == true ]]; then
find "$dir" -mindepth 1 -maxdepth 1 -mtime +"$days" -exec rm -rf {} +
echo -e " ${GREEN}✓ Deleted ${count} old items${NC} ${DIM}${label} (>${days}d)${NC} (${size_hr})"
else
echo -e " ${YELLOW}✗ Would delete ${count} old items${NC} ${DIM}${label} (>${days}d)${NC} (${size_hr})"
fi
TOTAL_FREED=$((TOTAL_FREED + size_kb))
}
# ─── Header ───────────────────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${CYAN}║ Claude Code Cache Cleaner ║${NC}"
echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════════╝${NC}"
echo ""
if [[ "$FORCE" != true ]]; then
echo -e "${YELLOW}⚠ DRY-RUN MODE — No files will be deleted. Use -f to apply.${NC}"
else
echo -e "${RED}⚠ FORCE MODE — Files WILL be deleted!${NC}"
fi
if [[ "$AGGRESSIVE" == true ]]; then
echo -e "${RED}⚠ AGGRESSIVE — Including sessions, history, and usage data.${NC}"
fi
echo ""
# ─── Global Cleanup ───────────────────────────────────────────────────────────
if [[ "$PROJECT_ONLY" != true ]]; then
echo -e "${BOLD}📦 Global Cache: ${CLAude_DIR}${NC}"
echo -e "${DIM}─────────────────────────────────────────────────${NC}"
# ── Safe to delete entirely ──
clean_item "$CLAude_DIR/cache" "Cache files" "safe"
clean_item "$CLAude_DIR/debug" "Debug logs" "safe"
clean_item "$CLAude_DIR/paste-cache" "Paste cache" "safe"
clean_item "$CLAude_DIR/downloads" "Downloads" "safe"
clean_item "$CLAude_DIR/stats-cache.json" "Stats cache" "safe"
# ── Clean contents (preserve directory) — only files older than 3 days ──
# Using time-based cleanup to avoid deleting files from active sessions
clean_old_files "$CLAude_DIR/file-history" "File edit history" 3 "safe"
clean_old_files "$CLAude_DIR/shell-snapshots" "Shell snapshots" 3 "safe"
clean_old_files "$CLAude_DIR/session-env" "Session environments" 3 "safe"
clean_old_files "$CLAude_DIR/backups" "Backup files" 3 "safe"
clean_old_files "$CLAude_DIR/tasks" "Task history" 3 "safe"
clean_old_files "$CLAude_DIR/plans" "Plan files" 7 "safe"
# ── Aggressive-only — also time-limited to protect active sessions ──
clean_old_files "$CLAude_DIR/sessions" "Session metadata" 7 "aggressive"
clean_item "$CLAude_DIR/history.jsonl" "Command history" "aggressive"
clean_old_files "$CLAude_DIR/usage-data" "Usage data" 7 "aggressive"
# ── Clean stale projects ──
# NOTE: Claude Code encodes project paths with a complex scheme that doesn't
# simply map "/" to "-". Paths with CJK characters, spaces, or dots produce
# encoding patterns like "----" that cannot be reliably reversed.
# Instead, we only clean project dirs that have ONLY a sessions-index.json
# (meaning no real session data was ever written — just an empty index).
echo ""
echo -e "${DIM} Checking for empty/unused project directories...${NC}"
if [[ -d "$CLAude_DIR/projects" ]]; then
while IFS= read -r proj_dir; do
# Count total files in the project directory
file_count=$(find "$proj_dir" -type f 2>/dev/null | wc -l | tr -d ' ')
# If only 1 file and it's sessions-index.json → never had real sessions
if [[ "$file_count" -le 1 ]]; then
has_only_index=$([[ -f "${proj_dir}/sessions-index.json" ]] && echo "yes" || echo "no")
if [[ "$has_only_index" == "yes" ]]; then
clean_item "$proj_dir" "Empty project: $(basename "$proj_dir")" "safe"
fi
fi
done < <(find "$CLAude_DIR/projects" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
fi
# ── Clean old files in large directories ──
echo ""
echo -e "${DIM} Checking for old files (>${STALE_DAYS:-30} days)...${NC}"
# Clean stale scheduled tasks (not from current session)
if [[ -f "$CLAude_DIR/scheduled_tasks.json" ]]; then
clean_item "$CLAude_DIR/scheduled_tasks.json" "Scheduled tasks" "aggressive"
fi
echo ""
fi
# ─── Project-Level Cleanup ────────────────────────────────────────────────────
if [[ "$GLOBAL_ONLY" != true ]]; then
# Find all project-level .claude directories
echo -e "${BOLD}📂 Project-Level Cache${NC}"
echo -e "${DIM}─────────────────────────────────────────────────${NC}"
# Current project
if [[ -d ".claude" ]]; then
echo -e " ${CYAN}Current project:${NC} $(pwd)/.claude/"
# Clean worktrees
if [[ -d ".claude/worktrees" ]]; then
worktree_count=$(find .claude/worktrees -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
if [[ "$worktree_count" -gt 0 ]]; then
# Check if any worktrees are still registered in git
stale_worktrees=0
while IFS= read -r wt_dir; do
if ! git worktree list 2>/dev/null | grep -q "$(basename "$wt_dir")"; then
stale_worktrees=$((stale_worktrees + 1))
fi
done < <(find .claude/worktrees -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
if [[ "$stale_worktrees" -gt 0 ]]; then
echo -e " ${DIM} Found ${stale_worktrees} stale worktree(s) (not registered in git)${NC}"
# Only clean stale worktrees in force mode
if [[ "$FORCE" == true ]]; then
while IFS= read -r wt_dir; do
if ! git worktree list 2>/dev/null | grep -q "$(basename "$wt_dir")"; then
rm -rf "$wt_dir"
echo -e " ${GREEN}✓ Deleted stale worktree${NC} ${DIM}$(basename "$wt_dir")${NC}"
fi
done < <(find .claude/worktrees -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
else
echo -e " ${YELLOW}✗ Would clean stale worktrees${NC}"
fi
fi
fi
fi
# Clean project-level scheduled tasks
clean_item ".claude/scheduled_tasks.json" "Project scheduled tasks" "aggressive"
# Clean project-level session/task artifacts (but preserve memory/ and skills/)
for subdir in .claude/tasks .claude/plans .claude/session-env; do
if [[ -d "$subdir" ]]; then
clean_old_files "$subdir" "$(basename "$subdir")" 3 "safe"
fi
done
else
echo -e " ${DIM}No .claude/ directory in current project${NC}"
fi
echo ""
fi
# ─── Summary ──────────────────────────────────────────────────────────────────
echo -e "${BOLD}══════════════════════════════════════════════════${NC}"
total_hr=$(human_size "$TOTAL_FREED")
if [[ "$FORCE" == true ]]; then
echo -e "${GREEN}${BOLD} Freed: ${total_hr}${NC}"
else
echo -e "${YELLOW}${BOLD} Would free: ${total_hr}${NC}"
echo -e ""
echo -e " Run with ${BOLD}-f${NC} to apply: $(basename "$0") -f"
if [[ "$AGGRESSIVE" != true ]]; then
echo -e " Add ${BOLD}--aggressive${NC} for deeper clean: $(basename "$0") -f --aggressive"
fi
fi
echo -e "${BOLD}══════════════════════════════════════════════════${NC}"
echo ""
# ─── Preserved Items Info ─────────────────────────────────────────────────────
echo -e "${DIM}Preserved (not deleted):${NC}"
echo -e "${DIM} • Settings files (settings.json, settings.local.json)${NC}"
echo -e "${DIM} • Skills, agents, and plugins${NC}"
echo -e "${DIM} • Project memories (*/memory/)${NC}"
echo -e "${DIM} • IDE configuration${NC}"
echo -e "${DIM} • Harness rules${NC}"
echo ""到此这篇关于一文详解如何使用脚本清理Claude Code缓存信息的文章就介绍到这了,更多相关Claude Code缓存信息清理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章,希望大家以后多多支持脚本之家!
