shell-rsync-vanished-files-delete-race
Delivered (all in ~/snapshot-fix/):
- snapshot.sh — the hardened script
- test/snapshot_test.sh — the 56-assertion verification harness
- SNAPSHOT-HARDENING.md — the self-contained deliverable (title, root-cause analysis, exact fix, verification)
set -e treats rsync's exit 24 ("vanished source files" — benign, normal on busy trees) as fatal → rotation aborted.--delete ran against the live snapshot tree; a retry after an interrupted run deleted the only intact copy.rsync ... || rc=$? (the OR-list is what set -e can't kill before inspection). 0 and 24 → proceed; 11/12/23/25 and everything else → fatal, nothing published, previous snapshot kept. Real errors are never masked.--delete) only ever targets .staging; publish happens only after a successful transfer via one atomic mv (rename) + one atomic symlink swap of latest. The previous snapshot is used purely read-only as --link-dest (cheap hardlinks). Any failure discards staging via an EXIT trap.flock -w $LOCK_TIMEOUT on fd 9) held for the whole run, so transfer and rotate can never interleave; a blocked invocation times out and exits 1.The tests also caught and the fix addresses two latent bugs: same-second snapshot-name collisions (would nest a snapshot inside the previous one via mv semantics — now guarded with -NN suffixes) and retention pruning that kept the oldest N instead of the newest (now sort -zr); plus --max-delete as the tripwire against a momentarily empty source becoming a mass deletion.
# Evidence - Problem class: shell-rsync-vanished-files-delete-race - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-28T22:51:16.730Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A nightly snapshot script using rsync --delete under set -euo pipefail intermittently destroys the previous good snapshot: rsync exits 24 when files vanish mid-transfer, which the script treats as fatal and aborts the rotation, and the retry then runs --delete against a half-written destination, deleting the only intact copy. Harden the script so exit code 24 is tolerated without masking real errors (23/11/12 still fatal), deletions are only applied after a fully verified transfer via a staging dir plus atomic rename, and concurrent invocations cannot interleave the rotate step (flock-based mutex with timeout).", "environment": "bash", "language": "shell", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "shell-rsync-vanished-files-delete-race", "provider": "openrouter", "solved_at": "2026-08-28T22:51:16.731Z", "version": "5.2"}Delivered (all in ~/snapshot-fix/):
- snapshot.sh — the hardened script
- test/snapshot_test.sh — the 56-assertion verification harness
- SNAPSHOT-HARDENING.md — the self-contained deliverable (title, root-cause analysis, exact fix, verification)
set -e treats rsync's exit 24 ("vanished source files" — benign, normal on busy trees) as fatal → rotation aborted.--delete ran against the live snapshot tree; a retry after an interrupted run deleted the only intact copy.rsync ... || rc=$? (the OR-list is what set -e can't kill before inspection). 0 and 24 → proceed; 11/12/23/25 and everything else → fatal, nothing published, previous snapshot kept. Real errors are never masked.--delete) only ever targets .staging; publish happens only after a successful transfer via one atomic mv (rename) + one atomic symlink swap of latest. The previous snapshot is used purely read-only as --link-dest (cheap hardlinks). Any failure discards staging via an EXIT trap.flock -w $LOCK_TIMEOUT on fd 9) held for the whole run, so transfer and rotate can never interleave; a blocked invocation times out and exits 1.The tests also caught and the fix addresses two latent bugs: same-second snapshot-name collisions (would nest a snapshot inside the previous one via mv semantics — now guarded with -NN suffixes) and retention pruning that kept the oldest N instead of the newest (now sort -zr); plus --max-delete as the tripwire against a momentarily empty source becoming a mass deletion.
# Evidence - Problem class: shell-rsync-vanished-files-delete-race - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-28T22:51:16.730Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A nightly snapshot script using rsync --delete under set -euo pipefail intermittently destroys the previous good snapshot: rsync exits 24 when files vanish mid-transfer, which the script treats as fatal and aborts the rotation, and the retry then runs --delete against a half-written destination, deleting the only intact copy. Harden the script so exit code 24 is tolerated without masking real errors (23/11/12 still fatal), deletions are only applied after a fully verified transfer via a staging dir plus atomic rename, and concurrent invocations cannot interleave the rotate step (flock-based mutex with timeout).", "environment": "bash", "language": "shell", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "shell-rsync-vanished-files-delete-race", "provider": "openrouter", "solved_at": "2026-08-28T22:51:16.731Z", "version": "5.2"}