Diagnose Migration-History Divergence
Use when an EF Core deploy or rollback against a shared environment
(not a personal dev DB) surfaces a migration '<id>' was not found error,
or before running Update-Database / dotnet ef database update against a
shared environment as a rollback. Either situation means the target's
__EFMigrationsHistory may contain migrations the current branch doesn't
know about.
Why this is skill-shaped, not a one-off fix
Any team running EF Core migrations against a shared environment from multiple parallel branches can hit "the environment has migrations the current branch doesn't know about" — typically because someone applied an unmerged branch's migrations directly to that environment, whether as a deliberate shortcut or by accident. The failure mode shows up from two different entry points that look unrelated at first:
- A deploy pipeline fails applying a new migration with a "migration ... was not found" error that's confusing on its face, since the named migration usually isn't the one actually being deployed.
- A planned rollback (
Update-Database <id>or equivalent) silently targets the wrong point in history, or fails outright, because the branch doing the rollback also doesn't contain the divergent migrations sitting in the environment's history table.
Same root cause, same multi-step manual git trace, done by hand each time regardless of which entry point surfaced it. The diagnostic sequence generalizes even though the specific divergent migrations, branches, and authors differ every time it recurs.
Inputs
Ask for, or infer from the conversation, before starting:
- The target environment (e.g. a shared integration or staging
environment) and either a connection to query it directly, or a pasted
__EFMigrationsHistorydump / the error message naming the missing migration. - The local repo path and which branch is being deployed, or is about to run a migration command against that environment.
- The
Migrations/project path(s) — EF Core setups sometimes splitDbContextand migrations across projects, so get both--projectand--startup-projectifdotnet efcommands will be needed.
Procedure
1. Get the environment's actual applied-migration set
Query the target directly if you have a connection:
SELECT MigrationId, ProductVersion
FROM __EFMigrationsHistory
ORDER BY MigrationId;
Otherwise parse the migration ID out of the "was not found" error, or use
whatever __EFMigrationsHistory dump was pasted into the conversation.
2. Get what the deploying/current branch knows about
dotnet ef migrations list --project <MigrationsProject> --startup-project <StartupProject>
This lists every migration the branch's assembly contains (marking any not
yet applied to whatever DB it's currently pointed at). If dotnet ef isn't
runnable in context (e.g. wrong connection string reachable from here),
cross-reference by filename against Migrations/*.cs instead — each
migration's ID is the yyyyMMddHHmmss_Name prefix of its filename.
3. Diff to find the divergent set
Divergent migrations = present in the environment's __EFMigrationsHistory
but absent from the branch's Migrations/ folder. This is the set that
doesn't belong to any history the current branch has ever merged — and any
migration command (a deploy, or an ordinary Update-Database <target> used
for a rollback) that targets a point in the history before these
divergent entries can misfire, since EF walks __EFMigrationsHistory
sequentially and doesn't know how to skip entries it has no matching class
for.
4. Trace each divergent migration to its origin
For each divergent MigrationId, find the commit that introduced its file:
git log --all --diff-filter=A -- '**/Migrations/*<MigrationId>*.cs'
Confirm whether that commit ever reached the deploy target branch (usually
main):
git merge-base --is-ancestor <commit> main && echo "merged" || echo "NOT merged"
If not merged, identify the actual owning branch(es) and author:
git branch --all --contains <commit>
git log -1 --format='%an %ae' <commit>
5. Classify risk per divergent migration
Open each divergent migration's Up/Down bodies and classify:
- Lower risk — stored procedure, view, or function bodies only (e.g.
migrationBuilder.Sql("CREATE OR ALTER PROCEDURE ...")); noCreateTable,AddColumn,DropColumn,AlterColumn, or data-mutatingSql(...)calls. - Higher risk — schema changes (
AddColumn,DropColumn,CreateTable,AlterColumn) or data-mutating SQL. These can make a rollback destructive, or leave the environment's schema in a state the current branch's model doesn't expect.
6. Produce the hand-off report
State, per divergent migration: ID, origin branch, author, merge status
(merged / not merged, and where), and risk classification. Then state
plainly what the safe next step is for a human to choose from — for
example: "coordinate with <author> before touching this environment,"
"these N migrations are stored-proc-only and likely safe to leave in
place," or "do not run Update-Database <id> past this point without
confirming with <author> first."
Do not execute a rollback, a migration-history edit, or an
Update-Database command yourself. That decision, and its execution,
belongs to a human — especially against a shared or production-adjacent
environment.
Non-Goals
- This does not fix or roll back the divergence itself — diagnosis and risk classification only.
- This does not generalize to migration frameworks without an accessible,
greppable applied-migrations table and one-class-per-migration git
history (this depends on EF Core's
__EFMigrationsHistoryconvention). - This does not replace coordinating with the divergent migration's author before mutating a shared environment.