Skip to content
Back to Ideas 6 min read

What One Real Release Taught Us in an Afternoon

What One Real Release Taught Us in an Afternoon

Encoding the release, part 7 of 8.

Part 3 ended with a promissory note: our nulls fake the shapes of GitHub's answers but never its behaviour, so the real side of the gh wrapper would be proven by a real release rather than a faked GitHub. This is the afternoon the bill arrived. It cost two crashes and one near-miss, and it was the cheapest education we have ever bought.

Crash one: the race we had never met. The second run of ekohacks release 0.4.1 sailed through preflight, branched, bumped, pushed, opened PR #198 — and died:

Error: gh pr checks 198 failed:
no checks reported on the 'release/v0.4.1' branch

In the seconds between a PR opening and CI registering its checks, gh pr checks exits non-zero with "no checks reported". Our code treated any such failure as fatal. No test caught this because every test ran against our null — and we would never have thought to fake an error message we did not know existed. A hand-written fake GitHub would have encoded exactly the same ignorance and then certified it. Testing against a fake you wrote yourself proves only that you agree with yourself; reality is the only oracle for the boundary. The fix: "no checks reported" parses as an empty round, not an error.

The near-miss inside the crash. Fixing it exposed something worse. Had checks() quietly returned an empty list there, the cut policy considered zero checks "all concluded" — and would have merged the release PR before CI ever started. The crash, embarrassing as it was, had been standing guard over an insta-merge. Both fixes landed red-first: cut now waits while no checks are reported, pinned by a test that walks empty → green → merge.

Crash two: the human did a human thing. With PR #198 finished by hand, the operator did the natural thing — ran the whole command again. It crashed again:

fatal: a branch named 'release/v0.4.1' already exists

Why? The rail has no memory. The first attempt's local branch was still sitting there; the re-run started from the beginning, tried to create it again, and git — correctly — refused to overwrite the record of the earlier attempt. Preflight could not warn anyone, because every one of its checks was genuinely true: it verifies "is this a good place to start a release?", and has no concept of "this release is already half-done". Nothing about the release was broken. The stop protected the state. It just said so in the worst possible language: a stack trace.

That is the deepest lesson of the three. Our stories had promised that every stop condition "stops the rail with a named reason", which makes a raw stack trace not a cosmetic flaw but a specification violation. People re-run failed commands — twice, in our sample of one afternoon — so the failure mode of a stopped rail is not an edge case, it is the primary interface of a bad day. Cut now answers in its own voice:

stopped: branch release/v0.4.1 already exists from an earlier cut

We kept the no-resume design. Clean named stops plus standalone stages-as-recovery is still a better bargain than resume logic that must correctly diagnose every partial state. But we no longer believe the design is done when the happy path is; it is done when every way it stops has a name.

Three lessons, one afternoon, all found within minutes of touching reality — precisely because we had refused to fake it.

Next: The Release Is the Acceptance Test, which is still being written.

More from Ideas

·8 min read

Inversion of Control Doesn't Need a DI Container

We started designing a dependency scanner, and its needs led us to Inversion of Control. It turned out we were already doing it, without a DI container.

A
Anastasios Orfanidis
·7 min read

Unknown Is an Answer

A stub that holds no documents could not say whether a change still matched a query. Letting it answer unknown named a case the real database has too.

·7 min read

The Inbox That Delivers Nowhere

Signing in by emailed code puts a mailbox on the critical path. How we use Mailpit on Studomia, how our tests read the code, and what one question taught us.