Both ship scripts check the working tree before doing anything destructive:
if ! git diff --quiet || ! git diff --cached --quiet; then
fail "$REPO working tree dirty. commit/stash and re-run."
fi
The diff against staged and the diff against unstaged both have to be empty. If either is dirty, the script exits with a clear message and a clean recovery path: I commit or stash whatever I was in the middle of, then re-run. Nothing the script did so far gets undone, because nothing destructive has happened yet. The preflight is the first real step.
I was tempted to make the script auto-stash. It would save me a commit-or-stash step on the rare occasion I forget. I decided against it for one reason: a stash is invisible. If the script crashes in the middle of a long run and I forget about the stash, my work-in-progress is sitting in a stash list I never check. Auto-stash trades a known small annoyance for a rare large surprise.
The clean-tree gate also serves as a discipline. By forcing me to commit or stash before each ship, the script enforces that the about-page work is isolated from anything else I happen to have in flight. If I ship two unrelated changes by accident because they were both sitting in my working tree, the commits get tangled.
There’s a related preflight item: the script checks that the source bundle is intact (ten WebPs, AboutPhotoHero.astro, about.astro all present). Same principle. Both the consumer (the repo) and the producer (the bundle) must be in known states before anything moves.
The principle: the first thing a destructive script does is verify the world is in a state where its actions are reversible.