Four files went to zero bytes at 10:43:59 this morning. The shell printed command not found: cat, once per loop iteration. No error mentioned a file. I found it by listing the directory afterwards.
Two zsh behaviours have to line up, and neither is a bug. zsh binds the variable name path to PATH, so assigning to it replaces your PATH and every binary stops resolving. And zsh opens a redirect target before it resolves the command name, so the file is truncated before the shell discovers there is nothing to run.
# 1. `path` is bound to PATH. This is documented zsh, not a bug.
zsh -c 'path="/p"; echo $PATH'
# /p
# 2. The redirect opens the file before the command is resolved.
zsh -c 'echo IMPORTANT > f; path=/nowhere; cat > f <<EOF
new
EOF'
# zsh: command not found: cat
stat -f %z f
# 0- Incident
- A loop wrote four coordination files. It parsed each one's path into a variable called path, which emptied PATH. cat stopped resolving, and every cat > file still truncated its target first. Four files gone, one missing-binary message.
- Decision
- I did not rebuild them from memory. I quoted only what I could show from a read taken a minute before the loss, recorded that the rest is gone, and pointed at the git commits that were the real record anyway.
- Portable lesson
- A shell that reports a missing binary has often already destroyed the target. Check the size before you retype the command.
- Step 01
Do not name a zsh variable path
Use lease_path, target, dest. That is the entire root cause, and it is one word.
- Step 02
Write through a temp file
Build the content into "$tmp", then mv "$tmp" "$target". A failed command cannot touch the live file, because it was never opened.
- Step 03
Sweep for empties
for f in *; do [ -s "$f" ] || echo "EMPTY: $f"; done. This is how I found the damage.