Most Go migration tools assume that if the database is at version X, all earlier migrations have already been executed.
However, when working in a real team, this assumption breaks when migrations are created on parallel branches and merged out of order. Older migrations can be silently skipped, leaving the database schema incomplete while appearing “up to date”.
The fix is simple: execute migrations based on execution history, not version comparison. This guarantees that all unapplied migrations run and prevents schema drift in CI/CD pipelines.
While Go provides excellent libraries for application logic, the libraries and tools commonly used for database migrations have limitations, and their default “version-based” behaviour often lacks a standard, opinionated approach for safely managing schema evolution across teams and environments.
In most migration tools used in Go, each migration file is assigned a version identifier when it is created. This version is typically either:
During migration execution, the tool saves the latest successfully applied migration version. When migrations are run later, the tool assumes that all earlier versions have already been executed and applies only the migrations with a higher version number.
This model works well for linear or strictly ordered development. However, it introduces problems when migrations are created and merged out of sequence, which is common in real teams.
When tools rely on timestamp ordering rather than execution history, this mismatch creates a dangerous failure mode: silent schema skips.
In real teams, multiple developers often work on different feature branches simultaneously. When migrations are created in parallel branches and merged at different times, version ordering no longer reflects execution order.
To understand the issue, consider a typical morning for a Go team:
Depending on the tool and configuration, this can result in silent skips, warnings, or hard failures. In practice, the 09:30 migration is skipped, leaving the database missing a table or column in staging or production.
The above diagram illustrates how this failure occurs when migrations are created and merged out of order.
LiteBreeze first observed this issue when we started developing the large-scale coffee supply chain platform Era of We back in May 2019. While implementing database migrations across multiple active branches and environments, we encountered situations where migrations created earlier were skipped during deployment because a newer migration had already advanced the database version.
We contributed a fix in the Beego framework to shift the logic from checking “latest version” to checking “existence”, which was officially merged to solve this for the community.

Old Logic vs New Logic
The difference is subtle but critical. Instead of assuming earlier versions were applied, the system explicitly checks whether each migration has been executed. This guarantees convergence, regardless of merge order.
By 2021, other Go migration tools like pressly/goose began improving their handling of out-of-order migrations and introduced opt-in support for applying them, with further refinements in 2024.
CLI Usage:
Bash
# Explicitly allow applying migrations with older timestamps
goose up -allow-missing
Go Library Usage:
Go
// When using goose as a library in your Go code
goose.Up(db, “migrations”, goose.WithAllowMissing())
This allows older timestamped migrations to be executed even if the database version has already advanced.
However, many widely used Go migration tools such as golang-migrate, dbmate, and others do not support out-of-order migration execution as of 2026. These tools either fail when encountering mismatches or assume that earlier migrations were already applied.
As a result, teams must carefully coordinate branch merges and deployment timing to avoid schema inconsistencies.
From LiteBreeze’s perspective, this reinforced the need for an execution model that ensures correctness by default, rather than relying on discipline or manual coordination.
By treating migration execution as a function of execution history, rather than timestamp position, databases converge to the correct schema state even under non-linear development. Feature branches can be merged out of order without introducing schema drift or deployment risk.
Now that we’ve solved schema ordering, how should we handle data? Most tools treat seeding as an afterthought. Next, we will cover environment-aware, idempotent data seeding to unify your execution timeline.
Coming Next: The Data Seeding Gap