Skip to content

fix(storage): apply missing out-of-order migrations - #3092

Merged
illegalcall merged 1 commit into
mainfrom
fix/windows-missing-migrations
Jul 25, 2026
Merged

fix(storage): apply missing out-of-order migrations#3092
illegalcall merged 1 commit into
mainfrom
fix/windows-missing-migrations

Conversation

@illegalcall

Copy link
Copy Markdown
Collaborator

Summary

  • allow Goose to apply embedded migrations that are missing below the database's recorded maximum version
  • add a regression for a version-46 database missing migrations 28–31
  • verify Windows-style project paths and existing project data survive the repair
  • keep migration startup idempotent without modifying any shipped migration files

Root cause

Goose's default Up behavior rejects any embedded migration whose version is absent from the database history but lower than the database's maximum recorded version. A database produced by builds with later or renumbered migrations can therefore reach version 46 without versions 28–31 and permanently wedge daemon startup before the HTTP server is available.

Using Goose's supported WithAllowMissing option applies those authoritative embedded migrations in order instead of rejecting the history.

User impact

Affected Windows users can relaunch AO without moving, deleting, or resetting ~/.ao/data/ao.db. Existing project records are preserved.

Fixes #3091.

Validation

  • go test ./internal/storage/sqlite/... -count=1
  • go test -race ./internal/storage/sqlite/... -count=1
  • go vet ./internal/storage/sqlite/...
  • GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build ./...
  • regression confirmed failing before the fix and passing afterward

The full go test ./... -count=1 run reached two unrelated wall-clock-sensitive agent test failures under package-parallel load; both pass when rerun in isolation, and the SQLite packages remain green.

@illegalcall
illegalcall marked this pull request as ready for review July 25, 2026 08:17
@somewherelostt
somewherelostt self-requested a review July 25, 2026 08:23

@i-trytoohard i-trytoohard left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — LGTM ✅ (vetted locally)

Reviewed on behalf of #3091. I checked out the branch, ran the new test, the full internal/storage/sqlite/... suite, -race, and go vet — all green.

The fix is correct and minimal

One functional line — goose.Up(db, "migrations", goose.WithAllowMissing()) — plus a clear "why" comment. goose.WithAllowMissing() is goose's documented, supported option (up.go:19 in the pinned v3.27.1) for exactly this case: applying embedded migrations that are absent from the DB's history but sit below its recorded max version. Not a hack, not a flag we're repurposing.

This is the right lever. The alternative (detect + surface a friendly error, as I'd floated in the triage) would still leave the user wedged; WithAllowMissing actually unblocks them.

Safety

The 4 flagged migrations (28–31) are additive — ALTER TABLE ADD COLUMN, CREATE TABLE, CREATE INDEX, one projects table-rebuild. They apply in order against the existing schema. If a given legacy DB already contains one of those schema objects, goose surfaces a clear SQL error naming the offending migration — strictly better than the opaque "found 4 missing migrations" wedge, and no worse than the current hard crash (daemon's already dead for these users). No silent corruption path.

Test quality — good

TestMigrateAppliesMissingMigrationsBeforeCurrentVersion correctly reproduces the bug: seeds to v27, injects a bare v46 goose_db_version row (the exact state behind the error), then asserts:

  • all 4 missing migrations (28–31) apply,
  • pre-existing project data survives — including a Windows path C:\src\project-1, directly relevant to @maaz's Windows repro,
  • re-running migrate() is a no-op (idempotent).

Non-blocking observation

The test injects v46 as a bare version row (schema stays at v27's state), so it exercises the WithAllowMissing path against a v27 schema — not against a DB that already has the 28–31 objects (the legacy-TS-DB permutation). That's fine: testing every legacy permutation is impractical, and the nightly-churn case (the actual reported scenario) is covered. Worth knowing it exists, not worth blocking on.

Verdict

Clean, surgical, well-tested, uses the supported mechanism, unblocks affected users without data loss. Ready to merge.

@illegalcall
illegalcall merged commit 2aa92b9 into main Jul 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(storage): daemon fails to start on legacy TS-era DB — goose reports 4 missing migrations (dbMaxVersion=46 vs rewrite max 31)

3 participants