Encoding the release, part 6 of 8.
The first time ekohacks release ran against a real repository, for real, it produced
this:
ekohacks release 0.4.1
FAIL changelog entry: no ## 0.4.1 heading in the changelog
ok version is new
ok on main
ok clean working tree
ok lockfile registry
ok package smoke
stopped: preflight failed: changelog entry
We count this as the tool's first success, and we mean that without irony. Everything was in place — clean main, unpublished version, healthy lockfile, passing smoke — except the one input our process deliberately keeps human: the changelog entry, the piece of writing that becomes the PR body and the Release notes. The rail refused to start, named the missing piece, and touched nothing. A release tool earns trust by refusing well before it earns trust by shipping.
Then, while writing that entry, a question came up that sounded almost naive:
"So… do we deploy the local main, or origin/main?"
It is the sharpest question anyone had asked about the tool. The answer: what ships is always GitHub's main — the merge happens there, the Release is tagged there, the publish workflow runs there. But the rail builds on the local main: the release branch, the version bump and the PR all start from whatever commit it points at. The entire flow rests on an unstated promise that the two are the same commit.
And nothing checked that promise. Start from a stale local main — a teammate's merged
work never pulled — and one of two things happens. The loud one: the release PR
conflicts with origin's main, package.json being the classic battlefield, and the rail
dies half-done. The quiet one is the frightening one: nothing textually conflicts, the
PR merges green, and the published package silently omits work that was already on
main. Users install a version that does not contain what everyone believes main
contains, and no step of the process ever says a word.
Closing the gap followed the rule from part 1, in order. First the document:
RELEASING.md gained a new first line in its "Before cutting" checklist — main
matches origin/main, git pull before anything else — merged as its own PR. Then the
tool mirrored it, red-first, in five commits: the nulled git learned to answer a
configured sync state, an integration test pinned the real behaviour against a bare
origin, and preflight grew its seventh check:
ok on main
ok in sync with origin
ok clean working tree
The pattern to keep is not the specific check. It is that the checklist grew the way checklists should: one real question, one real near-miss, one line in the document, one test-first check in the tool. Preflight is not a list of clever ideas. It is a list of afternoons.


