Skip to content
Development

Auto-merge assumed CI existed. It didn't.

By Victor Da Luz
ciautomationcloudflaredev-logsite

The ask was one thing: make the article-shipping pipeline auto-merge blog PRs on green CI for imperfectsystems.com, the same way blog-manager already does. Should have been a copy-paste of an existing pattern. It wasn’t, because the premise was wrong.

The assumption that broke

blog-manager auto-merges because it has a self-hosted GitHub Actions runner producing a real pre-merge check on every PR. I went to do the same thing for imperfectsystems.com and found: no .github/workflows/ directory. Not disabled - never existed. The repo’s own docs say why: this repo doesn’t pay for Actions minutes. Deploys go through Cloudflare Workers Builds instead, Git-connected, triggered by a push to main.

So “wait for CI, then merge” needed a different signal. My first guess was that Workers Builds posts its own check to the PR, the way Actions would - Cloudflare does integrate with GitHub’s check API. I ran gh pr checks against a few real PRs to confirm before writing anything:

$ gh pr checks 26 --repo vdaluz/imperfectsystems.com   # this one's merged
Workers Builds: imperfectsystems-com  pass  ...production/builds/...

$ gh pr checks 24 --repo vdaluz/imperfectsystems.com   # this one's still open
no checks reported on the 'dependabot/npm_and_yarn/multi-368a2367aa' branch

The check only shows up after the PR merges - it’s Cloudflare reporting the production build that already ran, not a pre-merge gate. The docs explain why: non-production branch builds are disabled on this project (a KV-namespace collision bug, tracked separately). No preview build for the PR branch means nothing to attach a check to before merge.

That’s a real gap between what the issue assumed and what the repo can actually do today. I could have quietly worked around it and called it done, but the mismatch was worth writing down rather than papering over - added a KB note in case this account’s other sites ever hit the same assumption.

The substitute gate

Since there’s no signal to wait for, I made the gate synchronous instead: run the exact command Cloudflare’s build config uses, locally, on the commit that was just pushed -

npm run check && npm run format:check && npm run build

Same command, same repo state, no network round-trip to wait on. If anything, it’s a tighter gate than “waiting for CI” would have been, since there’s zero chance of the remote build silently skipping (as it currently does for these branches).

Where I got stopped, correctly

When I went to write the actual pipeline change - remove the “stop, print the merge command, wait” step and replace it with “check passes, merge immediately” - my own safety net blocked the edit. Not because the code was wrong, but because the change removes a human approval gate for a repo where merging deploys straight to production, and the only authorization on record was a general “go” earlier, not explicit sign-off on this specific mechanism. It forced the decision to be made explicitly.

That was the right call. I laid it out plainly - here’s what changes, here’s the risk - and made the explicit choice before touching the file. I’d rather eat that pause than have a permission system that rubber-stamps “remove human oversight from a production deploy path” because a plan two steps back gestured at it.

What’s next

The shipping pipeline’s imperfectsystems.com path now runs the build gate and merges in the same invocation on a pass; on a fail it leaves the PR open and stops, same as before. Recorded the authorization in project memory, mirroring blog-manager’s own auto-merge entry - but with the actual mechanism, since “wait for CI” doesn’t apply here. First real run should still be treated as supervised, same caveat the pipeline already carries for its other production-facing steps.

Related reading