The Approve Button Was Lying to Me

The Approve Button Was Lying to Me

*Last post I built pull requests for a studio of one. What I didn't mention: clicking Approve never actually checked whether the code worked. Here's the gate I bolted on afterward — and the day it did its job on me.*


In the last post I described the review pipeline I'd built for Deep Ice: every sub-task lives on its own Perforce stream, direct submits to anything above it are refused by a server trigger, and the only way up is a Helix Swarm review. Approve it, and a webhook does the rest — copies the stream up, deletes the spent branch, closes the Jira ticket.

I was proud of that setup. I was also, it turns out, being sloppy about what "Approve" meant.

The gap I didn't mention

Clicking Approve told the webhook one thing: a human looked at this and didn't object. It told it nothing about whether the code built, or whether the automated test suite that runs on every submit was green or on fire. Those two facts lived in completely different systems — TeamCity had the build result, Swarm had my opinion — and nothing made them talk.

I found out the hard way that this gap wasn't theoretical. More than once I approved and merged a review whose build had failed, because I hadn't gone and checked TeamCity before clicking the button. Not because I don't care about broken builds — because checking a second system by hand, every time, is exactly the kind of step a tired brain skips on the tenth review of the day. The process didn't stop me. It just trusted me to remember, and I didn't always.

That's not a workflow. That's a habit with a single point of failure, and the single point was me.

Why I couldn't just flip a switch

Perforce does ship a native way to connect Swarm to TeamCity, so I went looking for that first. Writing my own bridge between a review tool and a build server was not how I wanted to spend a week.

It doesn't fit how this project works. The native integration is built for pre-commit review — Swarm shelves your changes, TeamCity builds the shelf, and only then do you submit. Deep Ice's whole pipeline is the opposite: submit, then review. By the time a review exists, the changelist is already committed, so there's no shelf left for the native hook to build. I confirmed this the annoying way, watching a real review fail with an error about a shelved changelist that had never existed.

So the fix wasn't a checkbox in a settings page. It was a small always-on service — I call it the middleware — that sits between the two:

  • Swarm's workflow gets a new Tests entry that fires on every review update, marked Blocks: Approve. Swarm can't grey that out; it has to ask.
  • That call lands on the middleware, which asks TeamCity's REST API for the build tied to that exact changelist. Windows runs the automation tests; Linux is compile-only, so a missing Linux result isn't a failure — only a missing Windows one is.
  • The verdict — pass only if the build finished, succeeded, and reported zero failed tests and zero problems — goes straight back into the review as a pass/fail badge, with a link to the specific build.

Approve is no longer a button that trusts me. It's a button that has to ask a build server first, and the build server doesn't care whose day it's been.

The day it caught me

I'm suspicious of "it works!" posts about safety nets the author built themselves, so here's the part where mine got exercised for real.

I'd just landed a fix for three separate, previously invisible bugs in how a set of automated extraction tests were set up. That debugging saga is a post of its own; this one's about the gate. I verified the fix, submitted it, and the review's Tests badge came back Failed.

My mistake, not the fix's: I'd left the Unreal Editor open on the build agent while the automated run tried to launch its own instance, and the two collided. The build really did fail — just not because of anything in my change. I closed the editor, forced a clean rebuild, and it went green on the agent.

Here's the bit I care about: the review still said Failed. The gate didn't quietly forgive the bad build because a later one happened to pass. It had recorded a verdict for that changelist and had no reason to look again. "It's fine now, trust me" isn't supposed to move that needle, and it didn't.

The recovery wasn't a hack, either. There's a Rerun test button on the failed entry — available to anyone who can act on the review, not an admin backdoor. Clicking it fires the same webhook the original submit did: ask TeamCity again, for the current state, and report back. It came back Passed, because by then it was. I didn't touch a database, edit a status, or ask anyone to take my word for it. I asked the same impartial question a second time and got the true answer.

That's the design working as intended, including the part where it was briefly, correctly, in my way.

Why the boring failure mode is the good one

A gate like this is only worth building if I trust its "no" more than I trust myself on a Friday afternoon — which means it has to be allowed to say no to me specifically, with no quiet exception for "but I'm sure it's fine now." Mine did. It blocked a real red build, it didn't care that the red was my fault rather than the code's, and the only way through was to ask again and get an honest answer.

I'm still the only developer at Night Wraith. Today, the person the gate protected from a bad merge was me, tripping over my own mistake. That's fine — that's the point of building it now. The day this stops being a team of one, nobody will need to remember that "Approve" should mean the build passed. The gate already knows.


Deep Ice is a 1–4 player cooperative survival horror game built in Unreal Engine by Night Wraith Studio — developed in the open, including the day the safety net caught its own builder. More as it comes.