ory/hydra · error

database error on committing or rolling back transaction: %w

Error message

database error on committing or rolling back transaction: %w

What it means

In popx's transaction wrapper, if Commit (success path) or Rollback (error path) itself fails with a database error other than the tolerated sql.ErrTxDone / 'conn closed' cases, the wrapper wraps that dberr in this message and returns it, obscuring but preserving the commit/rollback failure.

Source

Thrown at oryx/popx/transaction.go:118

					err = callback(WithTransaction(ctx, cn), cn)
					var dberr error
					if err != nil {
						dberr = cn.TX.Rollback()
						if errors.Is(dberr, sql.ErrTxDone) {
							// Already rolled back by the database (e.g. context cancelled).
							return err
						}
						if dberr != nil && dberr.Error() == "conn closed" {
							// pgx closes the connection on context cancellation before
							// database/sql gets a chance to roll back.
							// See https://github.com/jackc/pgx/issues/2551
							return err
						}
					} else {
						dberr = cn.TX.Commit()
					}
					if dberr != nil {
						return fmt.Errorf("database error on committing or rolling back transaction: %w", dberr)
					}
					return err
				}()
				if err == nil || !errors.Is(sqlcon.HandleError(err), sqlcon.ErrConcurrentUpdate()) {
					return err
				}
			}
			return err
		})
	}

	// SQLite and unknown dialects: opts are ignored; use pop's default
	// transaction path with concurrent-update retry handling.
	var err error
	for attempt := range MaxTransactionRetries {
		err = conn.Transaction(func(tx *pop.Connection) error {
			return callback(WithTransaction(ctx, tx), tx)
		})

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Check the wrapped %w cause for the underlying driver error (context canceled, connection reset, etc.)
  2. Retry the whole transaction operation — the wrapper already retries concurrent-update (40001) failures, but commit-time connection loss needs an application-level retry
  3. Reduce transaction duration and set appropriate statement/idle timeouts so the DB does not kill the connection mid-commit
  4. Ensure callers pass a context that stays alive until the transaction completes
  5. Inspect DB/proxy logs (PgBouncer, LB idle timeouts) for connection teardown at commit time

Example fix

// before
err := trx(ctx, conn, func(ctx context.Context, tx *pop.Connection) error { ... }) // one-shot, fails on commit drop
// after
for i := 0; i < 3; i++ {
    if err := trx(ctx, conn, callback); err == nil || !isConnectionError(err) {
        return err
    }
    time.Sleep(backoff(i))
}
Defensive patterns

Strategy: retry

Validate before calling

func withinDeadline(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc, error) {
    if ctx.Err() != nil {
        return nil, nil, fmt.Errorf("context already cancelled before transaction: %w", ctx.Err())
    }
    return context.WithTimeout(ctx, d), nil, nil
}

Try / catch

var err error
for attempt := 0; attempt < 3; attempt++ {
    err = withTransaction(ctx, conn, callback)
    if err == nil {
        break
    }
    var dbErr interface{ Unwrap() error }
    if !errors.As(err, &dbErr) || !isTransientConnError(errors.Unwrap(err)) {
        break // permanent error, do not retry
    }
    time.Sleep(time.Duration(1<<attempt) * 100 * time.Millisecond)
}

Prevention

When it happens

Trigger: Calling conn.WithTransaction-style wrapped transactions where the connection drops between the callback and Commit/Rollback; database restart or network cut mid-transaction; context cancellation racing with Commit; pgx connection closed unexpectedly.

Common situations: Long-running transactions hitting idle timeouts; Kubernetes killing connections during deploys; context deadlines expiring exactly at commit time; transient network blips with Postgres-compatible stores (CockroachDB, YugabyteDB).

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/f1f1cc2bd985f698. Report an issue: GitHub.