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

  1. Inspect the wrapped cause for the syscall-level reason
  2. Keep the backing file alive for the duration of the upload (no concurrent deletion/cleanup)
  3. Use a real file-backed ReadSeeker rather than a partially-implemented wrapper
  4. 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

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


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