benbjohnson/litestream · warning

rollback post-checkpoint tx: %w

Error message

rollback post-checkpoint tx: %w

What it means

After the boundary snapshot, Litestream releases the checkpoint's write transaction with rollback(tx) (the lock-table insert is intentionally discarded). This error wraps a failure of that rollback. Because the transaction is deliberately discarded, a rollback failure here is a hygiene problem, but leaving the write transaction open would block other writers, so it is reported.

Source

Thrown at db.go:2627

		})
	snapshotInfo := syncInfo{
		offset:       WALHeaderSize,
		salt1:        binary.BigEndian.Uint32(other[16:]),
		salt2:        binary.BigEndian.Uint32(other[20:]),
		snapshotting: true,
		reason:       "checkpoint boundary snapshot",
	}
	result, err = db.sync(ctx, true, exec, snapshotInfo, 0)
	if err != nil {
		return false, fmt.Errorf("cannot snapshot after checkpoint: %w", err)
	}
	exec.applySyncResult(result)

	// Release write lock before exiting.
	// Use rollback() helper for consistency with releaseReadLock() and the
	// defer above. See issue #934.
	if err := rollback(tx); err != nil {
		return false, fmt.Errorf("rollback post-checkpoint tx: %w", err)
	}

	exec.state.syncedSinceCheckpoint = false
	return true, nil
}

// execCheckpoint issues a wal_checkpoint PRAGMA in the given mode and returns
// the number of frames in the WAL as reported by the checkpoint.
func (db *DB) execCheckpoint(ctx context.Context, mode string) (walFrameN int, err error) {
	// Ignore if there is no underlying database.
	if db.db == nil {
		return 0, nil
	}

	// Track checkpoint metrics.
	t := time.Now()
	defer func() {
		labels := prometheus.Labels{"mode": mode}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check the wrapped error; if it is 'sql: transaction has already been committed or rolled back', the tx was already torn down and the write lock is free — safe to proceed.
  2. If a context cancellation caused it, avoid canceling the ctx passed to Litestream operations during checkpoints.
  3. Verify no concurrent db.Close() is running during replication.
  4. Retry; subsequent checkpoints will attempt teardown again.
Defensive patterns

Strategy: try-catch

Try / catch

if err != nil && strings.Contains(err.Error(), "rollback post-checkpoint tx") {
    if strings.Contains(err.Error(), "already been committed or rolled back") {
        // benign: lock already released, safe to continue
    } else {
        // investigate driver/connection state
    }
}

Prevention

When it happens

Trigger: rollback(tx) fails after a successful boundary snapshot: the transaction was already invalidated by a driver error, the connection was closed, or the context driving the transaction was canceled.

Common situations: Context cancellation mid-checkpoint (app shutdown, deadline) causing the driver to abort the tx; connection dropped due to database file issues; rare driver-level errors in modernc.org/sqlite.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/0bfa2ff62615e803. Report an issue: GitHub.