benbjohnson/litestream · error
begin: %w
Error message
begin: %w
What it means
To take a boundary snapshot at the end of a checkpoint, Litestream begins a transaction (BeginTx) that it will later promote to a write transaction via a lock-table insert. This error wraps a failure to begin that transaction. It means SQLite refused to start the transaction, typically because the database handle is unusable or the connection cannot be obtained.
Source
Thrown at db.go:2592
if mode != CheckpointModeTruncate && walFrameN <= preCheckpointFrameN {
result, err = db.verifyAndSyncWithExecutor(ctx, true, exec, 0)
if err != nil {
return false, fmt.Errorf("cannot copy wal after checkpoint: %w", err)
}
exec.applySyncResult(result)
exec.state.syncedSinceCheckpoint = false
return true, nil
}
// Start a transaction. This will be promoted immediately after.
db.setSyncDiagPhase(diagPhaseCheckpointSnapshotBoundaryLock,
func(s *diagState) {
s.checkpointMode = mode
s.lastSyncedWALOffset = exec.state.lastSyncedWALOffset
})
tx, err := db.db.BeginTx(ctx, nil)
if err != nil {
return false, fmt.Errorf("begin: %w", err)
}
defer func() { _ = rollback(tx) }()
// Insert into the lock table to promote to a write tx. The lock table
// insert will never actually occur because our tx will be rolled back,
// however, it will ensure our tx grabs the write lock. Unfortunately,
// we can't call "BEGIN IMMEDIATE" as we are already in a transaction.
if _, err := tx.ExecContext(ctx, `INSERT INTO _litestream_lock (id) VALUES (1);`); err != nil {
return false, fmt.Errorf("_litestream_lock: %w", err)
}
// Copy anything that may have occurred after the checkpoint.
db.setSyncDiagPhase(diagPhaseCheckpointSnapshotBoundary,
func(s *diagState) {
s.checkpointMode = mode
s.lastSyncedWALOffset = exec.state.lastSyncedWALOffset
})
snapshotInfo := syncInfo{View on GitHub (pinned to 4ed7a308f6)
Solutions
- Check the wrapped error: 'context canceled/deadline exceeded' means the checkpoint ctx timed out — increase timeouts or reduce checkpoint frequency.
- Ensure the application does not close the *sql.DB while Litestream replication is running.
- Raise connection pool limits (db.SetMaxOpenConns) if other queries are starving the pool.
- Retry the sync; transient begin failures resolve on the next cycle.
Example fix
// before ctx, cancel := context.WithTimeout(ctx, 2*time.Second) // after ctx, cancel := context.WithTimeout(ctx, 30*time.Second) // allow checkpoint + snapshot to finish
Defensive patterns
Strategy: try-catch
Validate before calling
select {
case <-ctx.Done():
return ctx.Err() // don't start a checkpoint with an already-cancelled context
default:
} Try / catch
if err != nil && strings.Contains(err.Error(), "begin:") {
if errors.Is(err, context.DeadlineExceeded) {
// lengthen timeout and retry
}
} Prevention
- Give checkpoint/sync contexts generous timeouts relative to WAL size.
- Avoid calling db.Close() concurrently with replication.
- Size the connection pool so Litestream can always get a connection.
- Check for 'database is closed' wrapped errors indicating lifecycle misuse.
When it happens
Trigger: db.db.BeginTx(ctx, nil) fails during the checkpoint boundary-snapshot phase: the *sql.DB is closed, the context is already canceled/timed out, or the driver cannot open a connection (too many open connections, connection pool exhausted).
Common situations: Context deadline exceeded because the checkpoint took too long; db.Close() called concurrently by application code; connection pool limits (MaxOpenConns) exhausted by other application queries.
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
- begin passive checkpoint barrier: %w
- rollback passive checkpoint barrier: %w
- rollback post-checkpoint tx: %w
- begin transaction: %w
- commit transaction: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/c5a878f2d18e0779.
Report an issue: GitHub.