fix: pre-clean transient dirs + repeat cleanup before every mackup attempt

Root cause: Claude Code recreates ~/.claude/debug/latest (a dangling
symlink to the current debug session) almost immediately after it is
deleted. The old cleanup ran once before the retry loop, so any retry
after a 2s sleep would hit a freshly recreated symlink.

Fix: move _ssr_mackup_preclean() inside the retry loop so it runs
immediately before each mackup attempt, leaving no window for apps to
recreate problematic files.

Also remove known transient subdirectories by path before the generic
socket/dangling-link sweep:
  .claude/debug  .cursor/logs  .vscode/logs  .npm/_logs

Configurable via SSR_MACKUP_PRECLEAN_PATHS (space-separated, $HOME-relative).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-12 12:17:44 +02:00
parent 1d18dd6a48
commit 3e54f0ca20
2 changed files with 44 additions and 18 deletions
+6
View File
@@ -20,6 +20,12 @@ SSR_MACKUP_SUBDIR=Mackup
# ~/.mackup/ssr-custom.cfg before each backup. Set to "false" to opt out. # ~/.mackup/ssr-custom.cfg before each backup. Set to "false" to opt out.
SSR_MACKUP_AUTODISCOVER=true SSR_MACKUP_AUTODISCOVER=true
# Subdirs (relative to $HOME) to delete before each mackup backup attempt.
# Handles transient dirs with dangling symlinks or sockets (debug sessions,
# log dirs) that shutil.copytree cannot copy.
# Built-in defaults: .claude/debug .cursor/logs .vscode/logs .npm/_logs
# SSR_MACKUP_PRECLEAN_PATHS=".my-app/tmp .other-app/run"
# Extra paths (relative to $HOME) to always include, space-separated. # Extra paths (relative to $HOME) to always include, space-separated.
# Example: ".easymcp .my-tool .config/my-cli" # Example: ".easymcp .my-tool .config/my-cli"
# SSR_MACKUP_EXTRA_PATHS="" # SSR_MACKUP_EXTRA_PATHS=""
+34 -14
View File
@@ -262,28 +262,48 @@ ssr::mackup::backup() {
fi fi
[[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root" [[ -n "$mackup_root" ]] && ssr::mackup::_materialize_icloud "$mackup_root"
# Pre-backup cleanup: remove file types that shutil.copy cannot handle. # ---------------------------------------------------------------------------
# # Pre-backup cleanup — remove artefacts that shutil.copytree cannot handle.
# 1. Unix socket files (-type s) — runtime artefacts, recreated on app start. # Run this immediately before each mackup attempt so that apps (e.g. Claude
# Cause: shutil.Error [Errno 102] Operation not supported on socket # Code) that recreate debug symlinks very quickly don't sneak back in.
local socket_count # ---------------------------------------------------------------------------
socket_count="$(find "$HOME" -type s 2>/dev/null | wc -l | tr -d ' ')" _ssr_mackup_preclean() {
if [[ "$socket_count" -gt 0 ]]; then # Known transient subdirectories that apps recreate constantly.
ssr::log "Removing $socket_count Unix socket file(s) before mackup backup (runtime artefacts)" # These contain debug logs / sockets, never real config.
# Extend via SSR_MACKUP_PRECLEAN_PATHS (space-separated $HOME-relative).
local -a transient_dirs=(
.claude/debug # Claude Code debug session links
.cursor/logs # Cursor IDE logs
.vscode/logs # VS Code logs
.vscode-insiders/logs
.npm/_logs # npm debug logs
)
local extra dir
for extra in ${SSR_MACKUP_PRECLEAN_PATHS:-}; do
transient_dirs+=("$extra")
done
for dir in "${transient_dirs[@]}"; do
[[ -e "$HOME/$dir" ]] && rm -rf "$HOME/$dir" 2>/dev/null || true
done
# 1. Unix socket files (-type s) — [Errno 102] Operation not supported.
local n; n="$(find "$HOME" -type s 2>/dev/null | wc -l | tr -d ' ')"
if [[ "$n" -gt 0 ]]; then
ssr::log " pre-clean: removing $n socket file(s)"
find "$HOME" -type s -delete 2>/dev/null || true find "$HOME" -type s -delete 2>/dev/null || true
fi fi
# 2. Dangling symlinks (-type l ! -e) — point to a non-existent target. # 2. Dangling symlinks (-type l ! -e) — [Errno 2] No such file.
# Cause: shutil.Error [Errno 2] No such file or directory: '<target>' n="$(find "$HOME" -type l ! -e 2>/dev/null | wc -l | tr -d ' ')"
local dangling_count if [[ "$n" -gt 0 ]]; then
dangling_count="$(find "$HOME" -type l ! -e 2>/dev/null | wc -l | tr -d ' ')" ssr::log " pre-clean: removing $n dangling symlink(s)"
if [[ "$dangling_count" -gt 0 ]]; then
ssr::log "Removing $dangling_count dangling symlink(s) before mackup backup"
find "$HOME" -type l ! -e -delete 2>/dev/null || true find "$HOME" -type l ! -e -delete 2>/dev/null || true
fi fi
}
local attempt rc=0 local attempt rc=0
for attempt in 1 2 3; do for attempt in 1 2 3; do
_ssr_mackup_preclean # clean immediately before each attempt
if mackup -f backup; then if mackup -f backup; then
return 0 return 0
fi fi