The rsync --delete incident of April 2026

From Nikipedia
Revision as of 2026-04-16T16:44:47 by Nik (talk | contribs) (Create article documenting the rsync --delete incident)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Summary

On the evening of 2026-04-14, a misformatted rsync --delete command provided by Claude destroyed large portions of the home directory on my MacBook Pro (m5). The command was intended to deploy a build artifact from m5 to m1, but due to a line break in the middle of the command, the shell split it into two commands. The first had only a source and no destination — and macOS rsync in local mode with --delete used the single path as both source and target, beginning to mirror a small build/ folder over the entire home directory.

The deletion was interrupted with Ctrl-C mid-run, but not before Keychains, an entire automation repo, an in-progress project with uncommitted work, and substantial parts of ~/Library/ had been wiped.

The command

Claude produced this rsync command, wrapped across two lines:

rsync -avz --delete -e "ssh -o AddressFamily=inet" /Users/nik/Projects/GitHub/OrganizeMoneyAndTaxes/dashboard/build/
   edmiidz@m1.local:/Users/edmiidz/Projects/GitHub/OrganizeMoneyAndTaxes/dashboard/build/

zsh split the command at the newline. rsync received only the source path (no destination) and, because macOS rsync in local mode with --delete treats a single argument as both source and target, began mirroring dashboard/build/ over m5's home directory root (/Users/nik/), deleting everything in ~ that wasn't present in dashboard/build/.

The second line (edmiidz@m1.local:...) was then executed as a separate command and rejected by zsh as "no such file or directory."

What was lost

  • ~/Library/Keychains/login.keychain-db — cascading Keychain failure, dialog flood until reboot.
  • ~/Scripts/ — entire personal automation repo (AWS/GCP tooling, DB manager, Quick Navigation, deployment scripts).
  • ~/Projects/GitHub/OrganizeMoneyAndTaxes/ — entire working copy, including uncommitted work from that session:
    • Foreign Amount display fix on TransactionDetail.js
    • FX parser changes in scripts/import-bank-statements.js
    • A new WorkSearch.js page and work-search.js API route
    • A new backfill-foreign-currency.js script
    • Uncommitted changes to App.js, Sidebar.js, and src/index.js
  • Portions of ~/Library/ affecting Mail, Google Drive Desktop sign-in, iMessage sign-in, etc.
  • Parts of ~/google-cloud-sdk/ (reinstallable).

What survived

  • ~/Desktop/, ~/Documents/, ~/Downloads/ — fully intact.
  • ~/Projects/GitHub/RusuAI-Mobile/ — intact.
  • ~/.ssh/, ~/.aws/, ~/.gitconfig/, ~/.claude/, ~/.config/ — intact.
  • m1 itself was untouched — the broken command never reached the network phase, so the other machine's filesystem and MySQL database (including in-session schema and data fixes) were unharmed.

Root cause

  • The command was presented to me broken across two lines in a way that looked like one command but wasn't. I pasted it as-is.
  • rsync --delete with a missing or ambiguous destination is catastrophic. There was no dry run (-n) first.
  • There was no safety review of the command structure before running it.

Lessons

  1. Never issue multi-argument shell commands broken across unquoted newlines. Always one line, or use explicit \ line continuations.
  2. rsync --delete requires an explicit dry-run (-n) first whenever the human can't visually verify that the destination is correct.
  3. Prefer git over rsync for deploying repo-shaped content across machines — commits provide a natural audit trail and can't accidentally --delete the wrong tree.
  4. Enable Time Machine. This would have been a 10-minute restore instead of days of recovery.

Category