The Deploy That Was Never Actually Working
This site had been quietly failing to deploy since May. Fixing it meant peeling back four unrelated problems that all looked like the same problem.
I rewrote the copy on this site — new bio, new project list, new blog posts — pushed to main, and went to go check the live result. Nothing changed. Not "changed but wrong." Nothing. The GitHub Actions workflow that's supposed to auto-deploy to Cloudflare Pages on every push to main had, it turned out, never once succeeded. Not tonight. Since May, when it was added.
Nobody had noticed because nobody had a reason to look. The repo looked fine, the commits looked fine, and michaelflanigan.pages.dev — the actual working preview URL — just... existed, frozen at whatever the last manual deploy had been. A green checkmark culture assumes someone's watching the checkmarks. Nobody was.
First root cause was the boring one: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID were never set as GitHub secrets on this repo, so wrangler had nothing to authenticate with. Every run had been failing at the same step for two months. Set the secrets, re-run — new failure, different step. Progress, technically.
Second cause: the workflow file pointed at a Cloudflare Pages project called michaelflanigan-v2. The actual project, the one serving traffic, was named michaelflanigan. Somewhere between setting this up in May and today, the project got renamed or recreated and the workflow file never caught up. Fixed the name, pushed, watched the Actions log — green. First successful automated deploy this repo had ever had.
That should have been the end of it. It wasn't, because the actual goal was michaelflanigan.com, not the .pages.dev subdomain, and the custom domain had been sitting in "deactivated" status since it was attached back in May. Same pattern as the workflow: set up once, never verified, quietly wrong the whole time.
Here's where it got layered. My Cloudflare API token was scoped to Pages and Workers, not DNS, so I couldn't read the zone's records to see what was actually configured — I could see the symptom (deactivated) but not the cause. Retrying activation through the dashboard got further: the real error was "CNAME record not set," meaning the DNS side had never been finished, not just failed. So we went to add it — and hit a fourth, completely unrelated problem. An old Cloudflare Worker, untouched since April, still had a Custom Domain binding on michaelflanigan.com from a previous version of this project that predated the Next.js rebuild entirely. Cloudflare won't let a Worker and a Pages project both claim the same hostname, so the CNAME had nowhere to attach until that binding was deleted.
Deleted it, added the CNAME for www, watched it go active within a couple minutes. Added the CNAME for the bare domain — same record type, same target — and it silently didn't save the first time, which looked identical to "still propagating" until a screenshot confirmed only one of the two records existed. Added it again, and both domains went active.
None of these four things caused each other. Missing secrets, a stale config value, an abandoned resource from an old project, and an incomplete DNS setup — four separate points of failure that had been sitting there independently, for months, each one silent until something forced a look. That's the actual lesson: "it's been broken" and "it's currently working" are not opposites you can infer from each other. A system that appears to work — because the fallback URL happened to still be live — gives you zero signal about whether the thing built on top of it actually functions. The only way to find any of this was to stop assuming the pipeline worked and start checking each layer as if it might not, one at a time, regardless of how unrelated the layers seemed.
The site's on its real domain now. It took four unrelated bugs sitting quietly on top of each other to get there.