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
- Ensure Close is called exactly once per driver instance; guard with sync.Once or check that Open succeeded before closing.
- Read the wrapped message: 'conn: <nil>, db: ...' tells you which handle failed and why; fix the root cause it reports.
- If the error is 'sql: database is closed' / 'conn closed', remove the redundant close call rather than retrying.
- 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
- Close each driver instance exactly once (sync.Once or explicit ownership)
- Do not defer Close on both the migrate instance and the raw driver for the same handle
- Log Close errors but treat 'already closed' as benign at shutdown
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
- conn: %v, db: %v
- conn: %v, db: %v
- conn: %v, db: %v
- conn: %v, db: %v
- unable to parse option x-migrations-table-quoted: %w
AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02).
Data as JSON: /api/errors/8708ef3c03864327.
Report an issue: GitHub.