benbjohnson/litestream · error

rollback passive checkpoint barrier: %w

Error message

rollback passive checkpoint barrier: %w

What it means

During a passive checkpoint, Litestream first opened a short 'barrier' transaction, then rolls it back before bumping the litestream sequence number. This error wraps a failure of that rollback call, meaning the barrier transaction could not be cleanly rolled back (e.g. the connection or database is in a bad state). Because the transaction was supposed to be discarded anyway, the wrapped error usually reflects a deeper database/connection problem rather than lost data.

Source

Thrown at db.go:2534

	if exec.state.lastSyncedWALOffset > WALHeaderSize {
		preCheckpointFrameN = int((exec.state.lastSyncedWALOffset - WALHeaderSize) / frameSize)
	}

	// Execute checkpoint and immediately issue a write to the WAL to ensure
	// a new page is written.
	db.setSyncDiagPhase(diagPhaseCheckpointExec,
		func(s *diagState) {
			s.checkpointMode = mode
			s.lastSyncedWALOffset = exec.state.lastSyncedWALOffset
		})
	walFrameN, err := db.execCheckpoint(ctx, mode)
	if err != nil {
		return false, err
	}

	if barrierTx != nil {
		if err = rollback(barrierTx); err != nil {
			return false, fmt.Errorf("rollback passive checkpoint barrier: %w", err)
		}
		barrierTx = nil
	}

	if err = db.bumpLitestreamSeq(ctx); err != nil {
		return false, fmt.Errorf("bump litestream seq: %w", err)
	}

	// If WAL hasn't been restarted, exit.
	db.setSyncDiagPhase(diagPhaseCheckpointVerifyRestart,
		func(s *diagState) {
			s.checkpointMode = mode
			s.lastSyncedWALOffset = exec.state.lastSyncedWALOffset
		})
	other, err := readWALHeader(db.WALPath())
	if err != nil {
		return false, err
	} else if bytes.Equal(hdr, other) {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check the wrapped inner error (%w) to find the real SQLite failure and fix that first (I/O, busy, closed handle).
  2. Ensure no concurrent db.Close() or another process is checkpointing/truncating the same WAL while Litestream runs.
  3. Retry the sync/checkpoint; a transient SQLITE_BUSY on rollback typically resolves on the next sync cycle.
  4. If the error persists, run 'litestream reset' or integrity_check the database; a corrupt WAL can break transaction teardown.
Defensive patterns

Strategy: retry

Validate before calling

if db == nil || db.SyncStatus() == litestream.SyncStatusError { /* recreate/reopen db handle before continuing */ }

Try / catch

ok, err := dbCheckpoint(ctx, mode)
if err != nil && strings.Contains(err.Error(), "rollback passive checkpoint barrier") {
    // inspect wrapped cause; log and let next sync retry
    log.Printf("checkpoint barrier rollback failed, will retry: %v", err)
}

Prevention

When it happens

Trigger: Running the internal passive-checkpoint path (db.checkpoint with CheckpointModePassive, e.g. during regular WAL sync) when rollback(barrierTx) fails: the underlying SQLite connection is busy/corrupt, the db handle was closed concurrently, or the driver returns an error rolling back an already-broken transaction.

Common situations: Database file closed or invalidated while a checkpoint is in flight (e.g. calling db.Close() concurrently); SQLite SQLITE_BUSY/IO errors on a slow or failing disk; using the library against a database whose WAL is being manipulated by another process.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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