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 ErrNoCompaction

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Ensure the consumer reads the snapshot stream to EOF before closing — never close early after the last page
  2. Check for an earlier CloseWithError on the pipe; the first error on the pipe is the root cause
  3. Retry the snapshot with the consumer holding the reader open until EOF
  4. 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

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


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