Skip to content

fix(db): don't mark migrations applied when non-optional statements fail (root cause of #61)#66

Open
RubyWolff27 wants to merge 1 commit into
mainfrom
fix/migration-record-only-on-success
Open

fix(db): don't mark migrations applied when non-optional statements fail (root cause of #61)#66
RubyWolff27 wants to merge 1 commit into
mainfrom
fix/migration-record-only-on-success

Conversation

@RubyWolff27

Copy link
Copy Markdown
Owner

Why

Issue #61 ("api-key creation 500s") had a deeper root cause than the route. The api_keys table never existed on the VPS Postgres, yet migration 107_create_api_keys.sql was recorded as applied (2026-05-26).

_apply_migrations wraps each statement in a SAVEPOINT and only aborts if all statements fail; on partial failure it logs a warning and records the migration as applied anyway. So a CREATE TABLE written in SQLite dialect (INTEGER PRIMARY KEY AUTOINCREMENT, datetime('now')) fails on Postgres, gets swallowed, and the migration is marked done — a permanent silent gap. The existing _ensure_critical_columns() band-aid exists precisely because of this failure class; it just didn't cover the api_keys table.

Fix

Distinguish optional from hard statement failures:

  • CREATE EXTENSION failures stay tolerated (e.g. pgvector not installed) — unchanged.
  • Any other failed statement is a hard failure → the schema change didn't take effect → do not record the migration as applied (commit what succeeded, retry next boot).

This fixes the whole class, not just api_keys, with no change to the intentional pgvector tolerance.

Note

The HTTP create-key route itself is fine on Postgres — execute_insert already uses RETURNING id. The only fault was the missing table. The api_keys table has already been created on prod manually (idempotent 107_create_api_keys_pg.sql) and an rf_pk_… key minted for the Sage MCP integration; this PR prevents the silent-gap recurrence.

Test

  • python -m py_compile clean; failure-classifier unit-checked (CREATE TABLE/INDEX/ALTER → hard; CREATE EXTENSION → optional).
  • Full suite needs a Postgres DATABASE_URL — deferred to CI.

🤖 Generated with Claude Code

_apply_migrations recorded a migration in schema_migrations even when its
statements hard-failed (only raising if ALL failed). A CREATE TABLE written
in SQLite dialect (INTEGER PRIMARY KEY AUTOINCREMENT) fails on Postgres, the
SAVEPOINT swallows it, and the migration is still marked applied — leaving a
permanent silent gap. This is exactly why _ensure_critical_columns exists as
a band-aid, and is the root cause of the missing `api_keys` table (issue #61:
api-key creation 500s because the table never got created on the VPS Postgres
while migration 107 was marked applied).

Fix: classify per-statement failures. CREATE EXTENSION failures stay tolerated
(e.g. pgvector not installed); any other failed statement is a "hard" failure.
If a migration has hard failures (but not all-fail), commit what succeeded but
do NOT record it as applied, so it retries on the next boot instead of going
silently missing.

Co-Authored-By: Claude Opus 4.8 (1M context) <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.

1 participant