benbjohnson/litestream · error
s3: size ltx file %s: %w
Error message
s3: size ltx file %s: %w
What it means
uploadSizedLTX sizes the file by seeking to the end of the reader; when that Seek(0, io.SeekEnd) call fails, this error wraps the underlying cause. It means the io.ReadSeeker passed into the upload path could not be repositioned. This is an I/O failure on the local file, not a network/S3 problem.
Source
Thrown at s3/replica_client.go:779
// PutObject and the multipart uploader based on the object size.
func (c *ReplicaClient) uploadLTX(ctx context.Context, key string, r io.Reader, partSize int64) (int64, time.Time, *string, error) {
// The L0 replication path passes the local LTX file, so the size is
// known up front and the body stays seekable for SDK retries.
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)View on GitHub (pinned to 4ed7a308f6)
Solutions
- Check the wrapped error (the %w cause) for the actual syscall failure (e.g. file not found, bad file descriptor)
- Ensure the file backing the reader still exists and the disk is healthy at upload time
- Pass a plain *os.File or other reliably seekable reader; avoid custom Reader wrappers that break Seek
- Retry the replication cycle — transient disk conditions often clear
Defensive patterns
Strategy: validation
Validate before calling
f, ok := r.(*os.File)
if !ok {
return fmt.Errorf("upload requires a seekable *os.File, got %T", r)
}
if _, err := f.Seek(0, io.SeekEnd); err != nil {
return fmt.Errorf("reader not seekable/healthy: %w", err)
} Type guard
func isSeekableFile(r io.Reader) bool {
_, ok := r.(*os.File)
return ok
} Try / catch
if err != nil && strings.Contains(err.Error(), "size ltx file") {
// local I/O problem on the reader; check disk and file lifetime
} Prevention
- Pass real file-backed readers to upload APIs
- Keep the backing file alive for the whole upload duration
- Monitor disk health and free space on the LTX staging volume
When it happens
Trigger: uploadLTX dispatches to uploadSizedLTX when the reader is seekable; the Seek to end fails because the underlying file descriptor is closed, the file was removed, or the reader reports an I/O error (disk issue, fd exhausted).
Common situations: Filesystem errors on the LTX staging file (disk full, detached network volume); a bug in calling code that passes a wrapped reader whose Seek method errors; race where the temp file is deleted before upload.
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: rewind 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/2788553cee9b6901.
Report an issue: GitHub.