A cloud session pushed one post from a branch, and my whole site went back to August
A Claude Haiku 5.5 session in the cloud started from the repo's stale default branch, pushed its post on a side branch, and Cloudflare deployed that branch over the live site. For eight minutes aaronorelup.com had four posts. How it got through, how it was undone, and the four things that now stop it.
At 00:39 on 8 October, my time, a Claude Haiku 5.5 session running in the cloud pushed a branch to this blog’s repository, and about forty seconds later Cloudflare made that branch the live site. For eight minutes, aaronorelup.com was the site as it stood on 2 August: four posts, a plain dark list, no tea house, and none of the thirty-nine posts written since.
The session did nothing wrong by what it could see. It started from the branch GitHub called
the default, found three posts there, numbered its own AO-004, and pushed it to a side branch
rather than to main. Three settings that nobody had looked at since August did the rest.
Yesterday’s post, about videos that couldn’t seek, left this out, because one of the fixes still needed me. It doesn’t any more.
Three settings in a row
The default branch. The repo began on master. On 2 August I merged main into it twice,
then kept working on main and never went back. GitHub still listed master as the default
branch on the night it happened, frozen at AO-003. My laptop sessions never noticed, because my
local checkout was on main.
What a cloud session gets. A Claude Code session on claude.ai starts with a fresh clone of the
repository, on the default branch, and nothing else. The rules for this blog lived in a
CLAUDE.md one folder above the repo on my laptop: start from main, the next post number, how
publishing works. It had never been committed, so the cloud session never saw a line of it. The
commit it pushed says so in its own way:
2ac796e Thu Oct 8 05:39:08 2026 +0000 Claude
AO-004: scythe and serpent, painted with claude-paint
Co-Authored-By: Claude Haiku 5.5
public/paintings/scythe.png | Bin 0 -> 3319429 bytes
src/content/posts/scythe-and-serpent.md | 17 +++++++++++++++++
Two files on top of August. To that commit, the thirty-nine newer posts had never existed.
What Cloudflare did with a branch. The site is built by Cloudflare’s Workers Builds on every
push. “Builds for non-production branches” was switched on, and the command those builds ran was
npx wrangler deploy, which is the command that replaces the live site. So any branch pushed to
the repo went to production, not to a preview.
Undoing it
At 00:42 I asked a session on my laptop (Opus 5.5):
I had a haiku session post to my blog from the cloud and it accidentally reverted it back to a very old version, could you please correct the mistake and could you figure out why it happened and make sure it never happens again.
Its first look at the deploy history failed: wrangler deployments list answered that the Worker
does not exist. The repo’s config names it aaronorelup-blog; on Cloudflare it is called blog.
Asking the API for blog gave the history, and the problem with it: every deploy was made by the
same account through the same tool. Nothing marks one as a branch.
| Deployed (Central) | Version | Built from |
|---|---|---|
| 7 Oct 22:50 | 34b650ef | 5170ce2 on main: AO-038’s paintings |
| 8 Oct 00:39 | 0075e1af | 2ac796e on claude/pensive-hamilton-yuqj1a |
| 8 Oct 00:47 | 34b650ef again | rollback |
The commit-to-version pairs come from Cloudflare’s check run on each commit. At 00:47:58 it ran
npx wrangler rollback 34b650ef… --name blog, and seconds later the tea house answered again,
AO-041 and the oil paintings returned 200, and a video answered a byte range with 206. The site
had been wrong for 8 minutes 10 seconds.
The rest of the first turn, 16 minutes in all, went on the causes it could reach from a terminal.
It switched GitHub’s default branch to main, committed a short CLAUDE.md into the repo for
sessions that only ever see the repo, and added a guard to the build itself, tested both ways
before it was pushed (a fake branch build failed, a fake main build passed):
const ciBranch = process.env.WORKERS_CI ? process.env.WORKERS_CI_BRANCH : null;
if (ciBranch && ciBranch !== 'main') {
throw new Error(
`Not building "${ciBranch}" on Cloudflare: only main deploys aaronorelup.com. ` +
'Rebase the branch onto main and merge it there (see CLAUDE.md).',
);
}
It also said plainly what the guard can’t do: it lives in the new code, so master and the Haiku
branch, which don’t contain it, would still deploy if anyone pushed to them. That part needed the
Cloudflare dashboard, which its login couldn’t read.
The part that needed me
Builds
5170ce2frommainand deploys it: the last good version.Commits AO-004 on top of
master(2 August) and pushesclaude/pensive-hamilton-yuqj1a.Builds the branch and deploys it to production with
wrangler deploy. The site now has four posts.I ask the laptop session to fix it and find out why.
Rolls production back to version
34b650ef. Checks five pages and a byte range live.Pushes the build guard and the repo's own
CLAUDE.md(e537351), then makesmainthe default branch.Reports, and asks before two things: changing Cloudflare's setting in my Chrome, and deleting the two old branches.
yes do the cloudflare setting in chrome and delete the branches
Deletes
masterand the Haiku branch, noting the commits they can be restored from. Chrome is signed out of Cloudflare; it stops, since typing my password is mine to do.ok I'm signed in
Turns off builds for non-production branches, changes their command to
npx wrangler versions upload(uploads a version, never deploys), reloads to confirm it held.
The last change is belt and braces: if branch builds are ever switched back on, they now upload a
version that goes nowhere instead of replacing the site. So there are four separate stops, and a
stale branch has to get past all of them: the default branch is main, master is gone,
Cloudflare only builds main, and the build refuses to run on anything else.
The painting the Haiku session made is not published. It is one of several paintings of the same picture, and that is a different post.
Asking the site what it is
What bothered me afterwards was how the problem showed itself: the pages looked old. There was
nowhere to ask the live site which commit it was built from. So now there is. Every build writes
/version.json with the commit, the branch Cloudflare built, the time, and the newest listed
post. After a push, that commit should be the one just pushed. This button asks the live site
from your browser:
- Which build is this page from?
The repo’s instructions now tell every session to compare it with what it just pushed, so the next time a deploy and a push disagree, something other than my eyes can notice. Until this week the only alarm on this site was me happening to look at it, at 00:42, three minutes after it broke.