Part 1 covered the mechanism: a fingerprint gate decides whether a change ships in minutes over-the-air or needs a full store release. But a gate that only checks "is this native-safe" says nothing about whether the change is good. If every merge to main can reach production within minutes, your safety net can't be a release train that gives everyone time to notice a problem before it ships — it has to be built into the pipeline itself, because there's no train to catch it on the way out.
The PR gate
Every pull request into main runs through the same automated gate before it's mergeable: a type check, a lint pass, an automated test suite, and end-to-end checks against a real device build. None of that is negotiable — it's the floor, not a nice-to-have.
E2E is a big enough topic on its own — closing the loop between what a unit test can see and what actually happens on a phone in someone's hand — that it deserves its own dedicated post rather than a paragraph here.
jobs:
typecheck:
run: npm run typecheck
lint:
run: npm run lint
test:
run: npm test
e2e:
run: npm run e2e
Nothing exotic under the hood — ESLint for the lint pass, Husky for local pre-commit/pre-push hooks so the same checks catch you before CI even runs, Jest as the test runner, and React Native Testing Library for component-level tests. Popular, boring, well-documented tooling on purpose — the pipeline's value is in how these are wired together and gated, not in any one tool being clever.
Feature flags are the real safety valve
Here's the entry condition that makes OTA-from-main safe at all: shipping code and releasing a feature are two different actions. A merge can put new code on every user's device within minutes — that's deploy. Whether that code actually does anything visible is a separate switch, controlled by a remote feature flag, not by whether the code merged.
That decoupling is what makes trunk-based development survivable. Nobody has to get the timing of a merge exactly right, because "merged" doesn't mean "live to users" — it means "available, dark, until someone flips the switch."
if (remoteConfig.getBoolean('new_checkout_flow')) {
return <NewCheckoutFlow />;
}
return <LegacyCheckoutFlow />;
Anything not ready to be seen — a half-finished feature, a risky rewrite, a change you want to roll out to 5% of users first — goes behind a flag before it goes near main. That's a team discipline, not a code review checkbox, and it's the one non-negotiable rule underneath everything else in this pipeline.
I've used both Firebase Remote Config and Flagsmith for this in production — different feature sets, same underlying idea. Firebase leans simpler (boolean/value flags, decent default targeting) if you're already in that ecosystem; Flagsmith gives you more control over rollout percentages and environments if flags are a bigger part of how your team ships. Either is fine. What matters is picking one and actually using it as the gate, not treating it as optional.
The same fingerprint gate, wearing a different hat
The fingerprint check from Part 1 isn't just a build-vs-OTA router — it's also what makes a hotfix routing decision possible at all. Because the gate always compares against the exact binary currently in the store (or the last successful OTA), the system always knows one thing with certainty: whether main right now could safely reach the devices that are live, or whether it's drifted natively since. That single fact is the entire input to what happens next during an incident.
Hotfixing without touching main
A production fire needs a fix routed to wherever users actually are — and after a few routine OTAs, that is very often not the same commit as the last full store release. Two protected, idle snapshots exist for exactly this:
- One pinned at the last full store release.
- One pinned at the last successful OTA, if a newer one shipped since.
A hotfix branches off whichever of those two is actually live, ships from there, and only afterward gets cherry-picked back into main. Neither snapshot branch is ever merged into — they're frozen markers, not moving targets, precisely so a hotfix never accidentally drags in unrelated work that happened to land on main in the meantime.
The whole shape of it
None of these four pieces — the PR gate, feature flags, the fingerprint check, the hotfix routing — does much on its own. Together, they replace what a release manager and a release train used to do by hand: catch bad code before it ships, decouple "shipped" from "visible," know instantly whether main can reach production safely, and know exactly where a hotfix belongs during an incident. That's the whole trade this series has been describing — slower-feeling ceremony traded for faster, more honest feedback loops.
There's a fifth piece coming eventually — testing an OTA update against the real production binary before any real user sees it, without a second build. That's a post of its own once it's actually shipped.




Top comments (0)