benbjohnson/litestream · error
s3: rewind ltx file
Error message
s3: rewind ltx file %s: %w
What it means
After sizing the file, uploadSizedLTX seeks back to the start offset so the upload sees the whole body; failure of this Seek produces this wrapped error. It signals the seekable reader could not be repositioned to 'start'. The upload aborts before any bytes are sent.
Solutions
- Inspect the wrapped cause for the syscall-level reason
- Keep the backing file alive for the duration of the upload (no concurrent deletion/cleanup)
- Use a real file-backed ReadSeeker rather than a partially-implemented wrapper
- Retry the replication; a later cycle creates a fresh reader
Defensive patterns
Strategy: validation
Validate before calling
if _, err := rs.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("reader cannot rewind: %w", err)
} Try / catch
if err != nil && strings.Contains(err.Error(), "rewind ltx file") {
// repositioning failed before upload; check file lifetime and storage
} Prevention
- Avoid custom io.ReadSeeker wrappers with partial Seek support
- Do not delete temp LTX files while an upload is in flight
- Retry replication cycles on transient local I/O errors
When it happens
Trigger: Same reader as the sizing step: the Seek(start, io.SeekStart) fails because the file was closed/deleted between calls, or the ReadSeeker implementation returns an error for SeekStart positioning.
Common situations: Temp LTX file removed by concurrent cleanup while the upload was in flight; custom reader wrappers with limited seek support; underlying storage errors mid-sequence.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- s3: size ltx file
- webdav: cannot seek temp file
- cannot remove tmp files
- cannot specify url & path for file replica
- close L0 file
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/cb8459bc3137f6f7.
Report an issue: GitHub.
Appendix: source
Thrown at s3/replica_client.go:783
if rs, ok := r.(io.ReadSeeker); ok {
if start, err := rs.Seek(0, io.SeekCurrent); err == nil {
return c.uploadSizedLTX(ctx, key, rs, start, partSize)
}
// Reader does not support seeking (e.g. piped file descriptor);
// nothing has been consumed, so treat it as an unsized stream.
}
return c.uploadStreamedLTX(ctx, key, r, partSize)
}
// uploadSizedLTX uploads from a seekable reader whose size is known.
func (c *ReplicaClient) uploadSizedLTX(ctx context.Context, key string, rs io.ReadSeeker, start, partSize int64) (int64, time.Time, *string, error) {
end, err := rs.Seek(0, io.SeekEnd)
if err != nil {
return 0, time.Time{}, nil, fmt.Errorf("s3: size ltx file %s: %w", key, err)
}
size := end - start
if _, err := rs.Seek(start, io.SeekStart); err != nil {
return 0, time.Time{}, nil, fmt.Errorf("s3: rewind ltx file %s: %w", key, err)
}
// Extract timestamp from LTX header, then rewind so the upload sees the
// full file.
hdr, _, err := ltx.PeekHeader(rs)
if err != nil {
return 0, time.Time{}, nil, fmt.Errorf("extract timestamp from LTX header: %w", err)
}
timestamp := time.UnixMilli(hdr.Timestamp).UTC()
if _, err := rs.Seek(start, io.SeekStart); err != nil {
return 0, time.Time{}, nil, fmt.Errorf("s3: rewind ltx file %s: %w", key, err)
}
input := c.putObjectInput(key, timestamp)
input.Body = rs
if size < partSize {
input.ContentLength = aws.Int64(size)View on GitHub (pinned to 4ed7a308f6)