multica-ai/multica · critical

apply migration %q: %w

Error message

apply migration %q: %w

What it means

Returned when conn.Exec of the migration SQL file itself fails while applying it. The migration runs on the loop's pinned connection holding the migration advisory lock, so a failure means the SQL in the .sql file was rejected by Postgres (syntax, dependency, permission, or existing object).

Source

Thrown at server/cmd/migrate/main.go:441

		if err != nil {
			return fmt.Errorf("read migration %q: %w", file, err)
		}

		// Run any pre-migration hook before the SQL file. Hooks
		// receive the *pgxpool.Pool (not the loop's pinned conn), so
		// they can acquire other session-level locks without
		// colliding with migrationAdvisoryLockKey. Hook failures
		// abort the run before schema_migrations is updated, so the
		// 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

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Read the wrapped Postgres error in the %w chain — it names the exact statement-level failure (relation already exists, syntax error, etc.).
  2. If the object already exists because the migration partially applied in a prior failed run, wrap destructive/creative statements in IF (NOT) EXISTS or roll back the partial objects by hand, then re-run.
  3. If this is an edited already-recorded migration, restore the original file or write a NEW migration instead — never edit an applied version.
  4. Re-run `migrate`; schema_migrations was not updated for this version, so it retries.

Example fix

-- before: fails on re-run after a partial failure
CREATE TABLE issue_links (...);

-- after: idempotent so a clean retry succeeds
CREATE TABLE IF NOT EXISTS issue_links (...);
Defensive patterns

Strategy: validation

Validate before calling

-- Dry-run each new migration on a schema snapshot (e.g. via a CI step that
-- clones prod schema and applies pending files) before it reaches production,
-- so apply failures surface in CI, not during deploy.

Try / catch

err := runMigrations(ctx, pool, opts)
if err != nil {
    var pgErr *pgconn.PgError
    if errors.As(err, &pgErr) {
        switch pgErr.Code {
        case "42P07", "42710": // duplicate_table, duplicate_object
            log.Printf("object already exists; migration may have partially applied: %v", pgErr.Message)
        case "42703", "42P01": // undefined column/table
            log.Printf("ordering problem: references an object created later: %v", pgErr.Message)
        }
    }
    return err
}

Prevention

When it happens

Trigger: Applying a migration file whose SQL fails: CREATE TABLE on an existing name, invalid syntax, reference to a column/table created in a later migration, insufficient privileges, or a statement cancelled by statement_timeout.

Common situations: Editing an already-applied migration file (checksum/content drift so it no longer matches the schema), splitting one migration into two and running them out of order, running migrations with a role that lacks CREATE privilege, or a concurrent process having created the same object.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/99d8d64e15f6087a. Report an issue: GitHub.