Summary
In a shared development database scenario (multiple developers sharing a single dev DB), the migration tracking table can record a version that has no corresponding migration file in the current checkout — because a peer applied a migration but their file isn't yet in your branch.
When this happens, `wheels migrate latest` produces output that looks like a successful operation but is actually a no-op refusal:
```
$ wheels migrate latest
Running migration: latest...
Migrating from 20260521120100 down to 20260521111543.
$
```
Then `wheels migrate up` says:
```
No pending migrations. Database is at version 20260521120100.
```
Even though `wheels migrate info` shows the new local migration as `[ ]` pending. The trio of outputs contradicts itself.
Root cause
The CLI sorts migrations by timestamp. When the DB's recorded "current version" timestamp is higher than the highest local migration file, `migrate latest` interprets the situation as "the DB is ahead of where you want it" and tries to roll DOWN to your latest local version. But because the file for the higher version doesn't exist, there's no `down()` to call, so nothing happens.
Suggested fixes
- Detect and warn: when the DB tracking table contains a version that has no matching file, print a clear warning:
```
Warning: database tracks migration version 20260521120100 but no matching file
exists in app/migrator/migrations/. This usually means a peer applied a
migration whose file isn't yet in your branch. Your new migrations need
timestamps newer than 20260521120100 to apply.
```
- Better directional output: "Migrating from X down to Y" should not appear when the user ran `migrate latest` (a forward command). At minimum print "Nothing to do — your latest local migration (Y) is older than the database's current version (X). Either pull peer migrations or renumber your files."
- Doc fix: add a section to migrator docs explaining the shared-DB pattern and how to choose safe timestamps.
Repro
- Insert a fake high-version row into the migration tracking table.
- Create a new migration file with an earlier timestamp.
- Run `wheels migrate latest` — observe confusing output and no-op behavior.
Repo / version
- CLI: `wheels 4.0.1`
- Wheels Core: `4.0.0-SNAPSHOT+1779`
Where found
Surfaced while executing DataPAI portal Phase 0 against Titan's shared development MSSQL database.
🤖 Found while executing a Claude-Code-driven implementation plan
Summary
In a shared development database scenario (multiple developers sharing a single dev DB), the migration tracking table can record a version that has no corresponding migration file in the current checkout — because a peer applied a migration but their file isn't yet in your branch.
When this happens, `wheels migrate latest` produces output that looks like a successful operation but is actually a no-op refusal:
```
$ wheels migrate latest
Running migration: latest...
Migrating from 20260521120100 down to 20260521111543.
$
```
Then `wheels migrate up` says:
```
No pending migrations. Database is at version 20260521120100.
```
Even though `wheels migrate info` shows the new local migration as `[ ]` pending. The trio of outputs contradicts itself.
Root cause
The CLI sorts migrations by timestamp. When the DB's recorded "current version" timestamp is higher than the highest local migration file, `migrate latest` interprets the situation as "the DB is ahead of where you want it" and tries to roll DOWN to your latest local version. But because the file for the higher version doesn't exist, there's no `down()` to call, so nothing happens.
Suggested fixes
```
Warning: database tracks migration version 20260521120100 but no matching file
exists in app/migrator/migrations/. This usually means a peer applied a
migration whose file isn't yet in your branch. Your new migrations need
timestamps newer than 20260521120100 to apply.
```
Repro
Repo / version
Where found
Surfaced while executing DataPAI portal Phase 0 against Titan's shared development MSSQL database.
🤖 Found while executing a Claude-Code-driven implementation plan