Skip to content

fix(upgrade): stop re-sealing step registry on every boot - #204

Open
rguedes wants to merge 1 commit into
goposta:mainfrom
rguedes:fix/upgrade-fresh-install-reseal
Open

fix(upgrade): stop re-sealing step registry on every boot#204
rguedes wants to merge 1 commit into
goposta:mainfrom
rguedes:fix/upgrade-fresh-install-reseal

Conversation

@rguedes

@rguedes rguedes commented Aug 17, 2026

Copy link
Copy Markdown

Problem

On every startup the server aborts with:

ERROR: duplicate key value violates unique constraint "upgrade_steps_pkey" (SQLSTATE 23505)
INSERT INTO "upgrade_steps" ... VALUES ('2026-05-31-personal-workspaces','dev',...), ...
ERROR msg=failed to run upgrade steps error=upgrade: bootstrap fresh install: ...

Root cause

runLocked only persists app.version when !IsDev(binaryVersion). A dev build therefore never writes the row, so readVersion keeps returning fresh = true on every boot, markAllApplied re-inserts the whole registry, and the insert collides with the rows written by the first successful boot.

Secondary effect: because a dev build is always "fresh", newly added registry steps get auto-sealed instead of applied, so they can never be exercised locally.

Fix

  • Persist the version inside the fresh-install branch, regardless of IsDev. The next boot takes the IsDev branch (version pin still skipped) and applyPending runs any new steps normally.
  • Make the seal idempotent with ON CONFLICT DO NOTHING, so databases already stuck in the broken state (steps present, no app.version) can boot again without manual SQL.

Testing

go build ./... and go vet ./internal/storage/migration/upgrade/ pass. No unit test added — the package has no database test harness (Postgres-only, no sqlite driver in the module).

🤖 Generated with Claude Code

Dev builds never call writeVersion, so no app.version row is ever created.
Each boot then reads the missing row as a fresh install and re-inserts the
whole step registry, failing with a duplicate key violation on upgrade_steps
and aborting startup.

Persist the version in the fresh-install branch as well, and make the seal
idempotent with ON CONFLICT DO NOTHING so databases already left in the
broken state (steps present, no app.version) can boot again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants