Operate the automation
Make the work idempotent
Design an automation so running it twice reaches the same correct state without duplicating or corrupting work.
An automation is idempotent when running it twice reaches the same correct state as running it once. This matters more for automations than for hand work, because automations rerun. Schedules fire again. A Quick Action gets clicked twice. launchd restarts a job that died mid-write.
An idempotent file workflow checks whether the output already exists before it does anything. It does not append the date again on every retry, turning one screenshot into acme-2026-08-03-2026-08-03.png on the second pass.
Three techniques
Stable destination names, temporary files, and an atomic final move. They do most of the work:
name="acme-$(date +%F).png"
destination="$HOME/Projects/acme/media/$name"
if [[ -e "$destination" ]]; then
echo "already processed: $name"
exit 0
fi
tmp=$(mktemp "$destination.XXXXXX")
cp "$HOME/Desktop/screenshot.png" "$tmp"
mv "$tmp" "$destination"
The destination name is stable. It’s computed from the input, not from “now, plus a counter”. Running twice computes the same name, hits the existence check, and exits cleanly with status 0. Exiting 0 matters: “nothing to do” is success, not failure.
The atomic part
The temporary file plus mv is what makes this safe against interruption. Within one volume, mv is a rename. Any other process sees either no file or the complete file. Never a half-copied one.
If the job dies during cp, the destination never existed. The next run starts over safely. The leftover .XXXXXX file is the only trace, and it’s harmless.
Notice there is no separate “processed” list. The mv itself is the record, because the destination’s existence is the marker. One less thing to keep in sync.
Verify it
Do what reality will do to you. Run the same fixture twice:
./sort-screenshots && ./sort-screenshots
# moved acme-2026-08-03.png
# already processed: acme-2026-08-03.png
The second run reports no change. Not another copy. Not an error.
The mistake to hunt for
A marker written before the work finishes.
Say the script logs “done”, or records the input as processed, and then crashes before the mv. Every future run skips a file that was never produced. The automation looks healthy and the work is missing.
Markers come last. Always after the state change they describe, never before. I check this line by line in every script that reruns.
Try this: run your sort script twice on the same screenshot. If the second run creates a second file, or fails, fix the destination name first and the existence check second.
Lesson completed