A Migration Timestamp Collision CI Couldn't See
Two migrations with identical timestamps silently skipped one in production.
· 2 min read

# A Migration Timestamp Collision CI Couldn't See
At Letterstory, two engineers each wrote a database migration on the same day, on separate branches, and both migration files ended up with timestamps earlier than the newest migration already merged into the base branch. Individually, both PRs passed CI. Merged in sequence, one of them silently never ran in production — the migration runner sorts and applies migrations by timestamp, and a file that sorts as "older" than one already applied gets skipped, with no error, no warning, and nothing in the deploy log that would tell you to go looking.
## Why green CI didn't catch it
CI runs migrations against a fresh database for each PR in isolation. In that context, a timestamp collision is invisible — the migration applies fine, because there's nothing else in the sequence to collide with yet. The collision only exists relative to the *base branch's* migration history at merge time, which is a check CI wasn't running. Two individually-correct, individually-green PRs combined into a broken production state, and the breakage was a schema that quietly diverged from what the application code assumed it could rely on.
This is a specific instance of a pattern I look for in any diligence pass on a team with more than one engineer touching schema migrations: does the CI pipeline validate ordering *against the merge target*, or only correctness in isolation. Most pipelines do the second and assume the first is automatic. It isn't, once migration authorship happens in parallel.
## The gate that closes it
The fix was a CI job that specifically blocks a migration whose timestamp predates the newest migration already present on the base branch — not a general "do migrations run" check, but a check aimed at exactly this ordering hazard. It runs at PR time, before merge, catching the collision at the point where it's a five-second rename instead of a silent production gap discovered days later by someone debugging a missing column.
The detail that matters: this only works because the runner's actual behavior (skip anything that sorts older than what's applied) was understood precisely before the gate was designed. A generic "run the migrations and see if it errors" check wouldn't have caught this, because the failure mode isn't an error — it's a silent skip. Building the right gate required knowing exactly how the tool being gated actually fails, not just that it can.
## What this means for reviewing migration pipelines
I now ask, specifically, what happens when two migrations from parallel branches land in an order the runner didn't expect. Most teams haven't tested that scenario, because it doesn't come up until the team is big enough for two people to be writing migrations in the same week — which is exactly the point at which it starts happening regularly and silently. A migration pipeline that's only ever been exercised by one author at a time hasn't actually been tested against the failure mode that matters most once a team scales past that.


