Skip to content
Oznog

3.1 · storage and backups · as-is

Never delete in the same command as an unverified copy

date
20260723
what happened
An agent ran an scp whose remote path contained escaped quotes. Modern OpenSSH scp speaks SFTP, so the quotes were literal path characters and the transfer failed silently. An unconditional rm -f in the same command deleted the originals anyway.
what it cost
Three irreplaceable voicemail recordings destroyed permanently. Only their transcripts survived, because transcription happened to have run first.
what changed
A standing rule with no exemptions: never put a deletion in the same command or statement as the transfer it depends on, because the transfer can fail silently and the delete runs anyway. Transfer, verify by hash or byte count that someone actually observes, then delete, as three separate steps.
the check now
Every subsequent deletion in the project is gated on a verified-copy check, a checksum or a count, before any rm runs. And a runbook that names a command names the operating system and version it was proven on.

An AI agent working through a large file-reorganisation project destroyed three of Christoph’s voicemail recordings in a single command. The command was meant to move files to a new location and clean up the source, but the move used a remote path with quote characters that a modern scp treats literally rather than expanding through a shell. The copy silently failed while looking like it might have worked. The delete that followed in the same command ran anyway, because nothing had checked whether the copy actually landed.

Every other transfer in the project used a script that verified a checksum before deleting. This one was a quick interactive command, exempted because it was small. That exemption was the entire failure. The fix was not a smarter script. It was a rule with no exceptions: a transfer, then an explicit verification step that someone or something actually looks at, then the delete, always as three separate observed actions, never folded together for convenience.

The command looked small, quick and obvious, and that is exactly why nobody thought to double-check it. The mechanism is worth recognising on your own systems. scp switched to SFTP as its transport, and SFTP does not run your path through a remote shell, so quoting that used to be necessary is now a way to name a file that does not exist. The failure is quiet. Only the deletion is loud.

The second lesson underneath it is about the tools themselves. The same command name does not mean the same program, or the same version, on every machine an agent reaches, and an agent that learned a command on one host will use it on the next as if it were the same. On Node0 the Macs, the Windows hosts and the Linux fleet all answer ssh and all differ underneath: macOS ships a version of rsync from 2006 that aborts on a flag every Linux rsync accepts and prints a usage page that reads like help; a shell alias on one machine replaced the listing command with a different program and broke every script that parsed its output; the Windows hosts speak PowerShell, not a Unix shell at all; and the scp above changed its transport between two releases of the same tool. Old Linux releases that some hardware depends on carry the same risk, though the site has not met that case yet. So a runbook that names a command names the operating system and the version it was proven on, and an agent moving to a different platform proves the command there before it trusts it, which on a destructive command means the three separate steps above (rule 27).

Source: node0 lessons v0.1, lesson 3.1. Sanitized: checklist v0.1, 20260921; recording descriptions removed; voice pass 20260921. Part of oznog.com/node0.