multica-ai/multica · critical
record migration %q: %w
Error message
record migration %q: %w
What it means
Returned when updating the schema_migrations bookkeeping row fails after the migration SQL applied successfully: the INSERT (up) or DELETE (down) of the version could not be executed. The schema change is applied but not recorded, leaving the version in a half-recorded state that a re-run must reconcile.
Source
Thrown at server/cmd/migrate/main.go:450
// same version retries cleanly on the next invocation.
if hook, ok := opts.Hooks[version]; ok && hook != nil {
slog.Info("running pre-migration hook", "version", version, "direction", opts.Direction)
if err := hook(ctx, pool); err != nil {
return fmt.Errorf("pre-migration hook for %q (%s): %w", version, opts.Direction, err)
}
}
if _, err := conn.Exec(ctx, string(sql)); err != nil {
return fmt.Errorf("apply migration %q: %w", file, err)
}
if opts.Direction == "up" {
_, err = conn.Exec(ctx, insertSQL, version)
} else {
_, err = conn.Exec(ctx, deleteSQL, version)
}
if err != nil {
return fmt.Errorf("record migration %q: %w", version, err)
}
fmt.Printf(" %s %s\n", opts.Direction, version)
}
return nil
}
// quoteQualifiedIdentifier safely quotes either an unqualified table
// name ("foo") or a schema-qualified name ("schema.foo") for embedding
// into a SQL statement. Postgres does not let parametrized queries
// supply identifiers, so we have to interpolate, but pgx.Identifier
// does the right escaping (double-quotes, embedded-quote handling).
//
// The accepted shape is exactly one or two dot-separated components.
// Names containing more than one dot are rejected outright rather than
// silently sanitized into a "schema"."b.c" reference, which is valid
// SQL but almost certainly not what the caller meant.View on GitHub (pinned to 2c0912b6ec)
Solutions
- Check the wrapped error: a unique violation on insert means the version row already exists — inspect schema_migrations and reconcile manually.
- If schema_migrations is missing, recreate it and repopulate applied versions before re-running.
- If the connection dropped, verify Postgres logs, then re-run migrate — up will re-apply SQL, so confirm the migration body is idempotent (IF NOT EXISTS) or manually mark the version.
- Never delete version rows for migrations whose changes are live; only for verified rolled-back ones.
Example fix
-- before: version applied but row missing, re-run would double-apply
-- inspect, then record manually:
SELECT * FROM schema_migrations ORDER BY version;
INSERT INTO schema_migrations (version) VALUES ('20260815000000');
-- after: migrate now skips the already-applied version Defensive patterns
Strategy: validation
Validate before calling
-- Pre-flight before migrating: confirm the bookkeeping table is sane. SELECT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_name = 'schema_migrations' ) AS has_table; -- If false, recreate it and backfill applied versions before running migrate.
Try / catch
err := runMigrations(ctx, pool, opts)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
// version row already inserted: schema is applied, bookkeeping is fine
log.Printf("version already recorded; treating as applied")
return nil // only after verifying the schema change really is present
}
return err
} Prevention
- Treat schema_migrations as the single source of truth; never manually insert/delete rows unless reconciling a verified half-applied state.
- After any failed migrate run, inspect schema_migrations before re-running.
- Ensure the migrating role owns schema_migrations.
When it happens
Trigger: The insertSQL/deleteSQL statement against schema_migrations errors — the table is missing/corrupt, the pinned connection was killed mid-run, a unique violation on re-inserting an already-present version, or the transaction/permissions on schema_migrations are wrong.
Common situations: schema_migrations was dropped or recreated manually; a prior crash left the version row inserted while the operator assumed it was absent; a DBA revoked INSERT on the migrations table from the migrating role.
Related errors
- pre-migration hook for %q (%s): %w
- apply migration %q: %w
- inspect concurrent index %q: %w
- relation %q exists but is not an index
- drop invalid concurrent index %s: %w
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/4749fddeafb86301.
Report an issue: GitHub.