来源:互联网 更新时间:2026-06-13 07:12
用Claude Code用了一个多月,某天随手一看,发现~/.claude目录居然飙到

先看看这些文件到底是怎么长起来的:
du -sh ~/.claude/*/ | sort -rh
| 目录 | 大小 | 存储内容 |
|---|---|---|
projects/ | 1.0 GB | 会话日志(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 ,但话说回来,谁还会去恢复一周以前的会话呢?
每个项目目录下都有一个memory/文件夹,里面藏着MEMORY.md——这可是Claude Code跨会话的
这里提供一个清理脚本,安全方面做了多层防护:
--execute参数绝不删任何东西保存到~/.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
文件: 6,806
目录: 246 (UUID会话)
节省空间: 1.3 GB
| 路径 | 原因 |
|---|---|
projects/*/memory/ | 持久记忆(MEMORY.md) |
CLAUDE.md | 全局指令 |
settings.json | 用户设置 |
commands/ | 自定义斜杠命令 |
plugins/ | 已安装的插件 |
history.jsonl | 命令历史 |
脚本里用了macOS的stat -f%z。Linux上得换成stat --format=%s,或者用wc -c < "$file"做跨平台兼容。
除了清理会话日志,还可以考虑一个更全面的清理脚本,覆盖全局缓存和项目级缓存:
#!/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 <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 a void 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 ha ve 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 ""
这个脚本的特点是任务分明:有安全模式(只清理缓存、旧文件),还有激进模式(连会话元数据、命令历史一并清理)。同时支持只清理全局缓存或只清理当前项目缓存,灵活性不错。注意它同样守护了memory/和配置文件等关键数据。
《Off Campus》第二季官宣:这对CP还在,但不再是主角
和平精英如何做到压枪稳-和平精英怎样才能压枪稳
客单价碾压宝马奥迪!极氪5月交付新车34377辆:连续4个月双增长
免费影视剧APP推荐
儿子穿新中式现身大会堂 马斯克罕见用中文回应:他正在学习普通话
HBO 奇幻剧《龙之家族》第三季定档 6 月 22 日,最终预告片曝光喉道海战
DOTA2 TI时隔七年重返上海!门票6月10日开抢,国服享受优先购买!
抖音最火沙雕男生网名(精选100个)
网络热词聊污是什么意思
帅气继父网名女生可爱英文(精选100个)
金铲铲之战s17六暗星卡莎阵容玩法构筑指南
我的末日校园海斗手游上线时间是哪天
阿里发布Qwen3.7-Max大模型,全球第五、国产第一
SpaceX狂揽AI人才,马斯克亲自面试且不看简历背景
免费看电影的软件推荐
蒙古上单是什么梗
晨字沙雕网名大全女生(精选100个)
帅到极致的网名女生霸气(精选100个)
短剧《情绪超市》剧情介绍
免费看片软件下载地址推荐
手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc