benbjohnson/litestream · error
close ltx snapshot encoder: %w
Error message
close ltx snapshot encoder: %w
What it means
Wraps a failure from enc.Close() finishing the LTX snapshot. LTX encoders write a final checksum/trailer on Close, so this fails if the trailing write into the pipe fails (consumer closed early) or the computed trailer is invalid (e.g. a prior encode step wrote bad data). The snapshot is incomplete when this fires and the whole stream is failed via pw.CloseWithError.
Source
Thrown at db.go:2890
MinTXID: 1,
MaxTXID: pos.pos.TXID,
Timestamp: time.Now().UnixMilli(),
WALOffset: walOffset,
WALSize: walSize,
WALSalt1: rd.salt1,
WALSalt2: rd.salt2,
}); err != nil {
pw.CloseWithError(fmt.Errorf("encode ltx snapshot header: %w", err))
return
}
if err := db.writeLTXFromDB(ctx, enc, walFile, commit, pageMap); err != nil {
pw.CloseWithError(fmt.Errorf("write snapshot ltx: %w", err))
return
}
if err := enc.Close(); err != nil {
pw.CloseWithError(fmt.Errorf("close ltx snapshot encoder: %w", err))
return
}
_ = pw.Close()
}()
return &snapshotReadCloser{PipeReader: pr, pos: pos}, nil
}
func snapshotHeaderWALRange(maxOffset, frameSize int64) (offset, size int64) {
if maxOffset <= WALHeaderSize || frameSize <= 0 {
return WALHeaderSize, 0
}
offset = max(maxOffset-frameSize, WALHeaderSize)
return offset, maxOffset - offset
}
// Compact performs a compaction of the LTX file at the previous level into dstLevel.
// Returns metadata for the newly written compaction file. Returns ErrNoCompactionView on GitHub (pinned to 4ed7a308f6)
Solutions
- Ensure the consumer reads the snapshot stream to EOF before closing — never close early after the last page
- Check for an earlier CloseWithError on the pipe; the first error on the pipe is the root cause
- Retry the snapshot with the consumer holding the reader open until EOF
- If it persists with correct consumer behavior, validate the produced LTX with 'litestream ltx' to spot upstream corruption
Example fix
// before rc, _ := db.SnapshotReader(ctx, pos) io.CopyN(out, rc, knownSize) // closes early, producer fails on Close // after rc, _ := db.SnapshotReader(ctx, pos) io.Copy(out, rc) // read to EOF so enc.Close() succeeds rc.Close()
Defensive patterns
Strategy: validation
Validate before calling
// consume the stream fully before closing
rc, err := db.SnapshotReader(ctx, pos)
if err != nil { return err }
if _, err := io.Copy(out, rc); err != nil { return err } // to EOF
if err := rc.Close(); err != nil { return err } Try / catch
err := streamSnapshot(ctx, db)
if err != nil && strings.Contains(err.Error(), "close ltx snapshot encoder") {
// verify consumer read to EOF; check earlier pipe errors
return fmt.Errorf("snapshot not finalized: %w", err)
} Prevention
- Always read snapshot streams to EOF (io.Copy, not io.CopyN)
- Validate finished snapshots with 'litestream ltx'
- Treat the first pipe error as the root cause when diagnosing
- Cancel contexts only when intentionally abandoning a snapshot
When it happens
Trigger: enc.Close() returns an error at the end of snapshot generation: pipe reader already closed/cancelled by the consumer, or an internal encoder error writing the closing checksum record.
Common situations: Consumer treats the data as complete and closes the reader before EOF (must read to EOF, not just to the last known byte); context cancellation racing the final write; a corrupted stream from an earlier silent failure.
Related errors
- encode ltx snapshot header: %w
- encode ltx frame (pgno=%d): %w
- write snapshot ltx: %w
- snapshot interval must be greater than 0
- new ltx encoder: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/efabf166bf2e4fb1.
Report an issue: GitHub.