golang-migrate/migrate · error

conn: %v, db: %v

Error message

conn: %v, db: %v

What it means

Postgres.Close closes both the underlying pgx connection pool (p.conn) and the legacy database/sql handle (p.db) and reports any failure from either. Because both errors are reported together with %v, one of them may print <nil>; the error means at least one Close failed. It typically indicates the connection was already closed or is unreachable during teardown.

Source

Thrown at database/pgx/pgx.go:244

		StatementTimeout:      time.Duration(statementTimeout) * time.Millisecond,
		MultiStatementEnabled: multiStatementEnabled,
		MultiStatementMaxSize: multiStatementMaxSize,
		LockStrategy:          lockStrategy,
		LockTable:             lockTable,
	})

	if err != nil {
		return nil, err
	}

	return px, nil
}

func (p *Postgres) Close() error {
	connErr := p.conn.Close()
	dbErr := p.db.Close()
	if connErr != nil || dbErr != nil {
		return fmt.Errorf("conn: %v, db: %v", connErr, dbErr)
	}
	return nil
}

func (p *Postgres) Lock() error {
	return database.CasRestoreOnErr(&p.isLocked, false, true, database.ErrLocked, func() error {
		switch p.config.LockStrategy {
		case LockStrategyAdvisory:
			return p.applyAdvisoryLock()
		case LockStrategyTable:
			return p.applyTableLock()
		default:
			return fmt.Errorf("unknown lock strategy \"%s\"", p.config.LockStrategy)
		}
	})
}

func (p *Postgres) Unlock() error {

View on GitHub (pinned to 01a9643f14)

Solutions

  1. Ensure Close is called exactly once per driver instance; guard with sync.Once or check that Open succeeded before closing.
  2. Read the wrapped message: 'conn: <nil>, db: ...' tells you which handle failed and why; fix the root cause it reports.
  3. If the error is 'sql: database is closed' / 'conn closed', remove the redundant close call rather than retrying.
  4. Ignore Close errors on process exit only deliberately (log them), since they rarely affect data integrity.

Example fix

// before
m, _ := source.Open(...)
d, _ := pgx.Open(dsn)
inst, _ := migrate.NewWithInstance(...)
inst.Close() // first close
defer inst.Close() // second close -> conn/db already closed
// after
inst, err := migrate.NewWithInstance(...)
if err != nil { ... }
defer inst.Close() // single close
Defensive patterns

Strategy: try-catch

Try / catch

var closeOnce sync.Once
closeDriver := func() {
    closeOnce.Do(func() {
        if err := drv.Close(); err != nil {
            log.Printf("driver close: %v (inspect conn:/db: parts)", err)
        }
    })
}
defer closeDriver()

Prevention

When it happens

Trigger: Calling Close on a Postgres driver instance after the underlying connection or sql.DB was already closed (double Close), or when the network/database has gone away so conn.Close or db.Close returns an error such as 'conn closed' or a context/network failure.

Common situations: Deferring Close twice (e.g. both in Open error-handling paths and in main); closing a shared driver instance from multiple places; infrastructure tearing down the DB connection before the app exits; nil-ish errors surfaced from a previously failed connection.

Related errors


AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02). Data as JSON: /api/errors/8708ef3c03864327. Report an issue: GitHub.