benbjohnson/litestream · error
s3: rewind ltx file %s: %w
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.
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)
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 %s: %w
- webdav: cannot seek temp file: %w
- config file not found
- lease not held
- failed to delete files:
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/cb8459bc3137f6f7.
Report an issue: GitHub.