Summary
The AO daemon crashes on startup with a goose: duplicate version 25 detected panic, rendering the nightly desktop build completely unusable. Two migration files claim version 25:
Error (from the nightly desktop app)
panic: goose: duplicate version 25 detected:
migrations/0025_worker_idle_outbox.sql
migrations/0025_session_cleanup_facts.sql
The UI shows "AO daemon failed to start — Daemon exited with code 2."
Repro
- Launch the current nightly desktop build (2026-07-23+).
- Daemon panics during DB migration; app is non-functional.
Root cause
#2836 claimed migration version 25 and landed first. #2853 was developed in parallel and also grabbed 25. Each PR's branch CI saw only one 0025_* file and passed the existing guard test; the collision only materialized once both merged to main.
Fix
Rename the later-landing file to the next free slot (0030):
git mv backend/internal/storage/sqlite/migrations/0025_session_cleanup_facts.sql \
backend/internal/storage/sqlite/migrations/0030_session_cleanup_facts.sql
The migration is purely additive (a new cleanup_generation column on sessions + a new session_cleanup_facts table with CDC triggers) and has no dependency on 0026–0029, so renumbering to 0030 is safe. Verified locally: TestMigrationVersionsAreUnique passes and the full internal/storage/sqlite test suite is green after the rename.
Why CI didn't catch this
TestMigrationVersionsAreUnique (added in #336) exists exactly to prevent this, and it does fail on the current main state. It passed on each PR because it runs against the PR branch, not the merged state — the classic parallel-PR migration-number collision. Worth considering a merge-time/post-merge check that runs the guard against main head.
/cc @yyovil — this is blocking the nightly. One-line git mv fix.
Summary
The AO daemon crashes on startup with a
goose: duplicate version 25 detectedpanic, rendering the nightly desktop build completely unusable. Two migration files claim version 25:0025_worker_idle_outbox.sql(PR fix(lifecycle): durably deliver worker-idle completions to the orchestrator #2836, landed 2026-07-22)0025_session_cleanup_facts.sql(PR feat(storage): add session cleanup-facts table and cleanup_generation (#2811) #2853, landed 2026-07-23)Error (from the nightly desktop app)
The UI shows "AO daemon failed to start — Daemon exited with code 2."
Repro
Root cause
#2836claimed migration version25and landed first.#2853was developed in parallel and also grabbed25. Each PR's branch CI saw only one0025_*file and passed the existing guard test; the collision only materialized once both merged tomain.Fix
Rename the later-landing file to the next free slot (
0030):git mv backend/internal/storage/sqlite/migrations/0025_session_cleanup_facts.sql \ backend/internal/storage/sqlite/migrations/0030_session_cleanup_facts.sqlThe migration is purely additive (a new
cleanup_generationcolumn onsessions+ a newsession_cleanup_factstable with CDC triggers) and has no dependency on0026–0029, so renumbering to0030is safe. Verified locally:TestMigrationVersionsAreUniquepasses and the fullinternal/storage/sqlitetest suite is green after the rename.Why CI didn't catch this
TestMigrationVersionsAreUnique(added in #336) exists exactly to prevent this, and it does fail on the currentmainstate. It passed on each PR because it runs against the PR branch, not the merged state — the classic parallel-PR migration-number collision. Worth considering a merge-time/post-merge check that runs the guard againstmainhead./cc @yyovil — this is blocking the nightly. One-line
git mvfix.