benbjohnson/litestream · error

extract timestamp from LTX header: %w

Error message

extract timestamp from LTX header: %w

What it means

WriteLTXFile peeks at the LTX header of the incoming stream to extract the transaction timestamp. If the reader does not begin with a valid LTX header, PeekHeader fails and this error wraps the cause. Because the reader is TeeReader'd, this happens before any bytes are uploaded.

Source

Thrown at oss/replica_client.go:245

	return result.Body, nil
}

// WriteLTXFile writes an LTX file to the replica.
// Extracts timestamp from LTX header and stores it in OSS metadata to preserve original creation time.
// Uses multipart upload for large files via the uploader.
func (c *ReplicaClient) WriteLTXFile(ctx context.Context, level int, minTXID, maxTXID ltx.TXID, r io.Reader) (*ltx.FileInfo, error) {
	if err := c.Init(ctx); err != nil {
		return nil, err
	}

	// Use TeeReader to peek at LTX header while preserving data for upload
	var buf bytes.Buffer
	teeReader := io.TeeReader(r, &buf)

	// Extract timestamp from LTX header
	hdr, _, err := ltx.PeekHeader(teeReader)
	if err != nil {
		return nil, fmt.Errorf("extract timestamp from LTX header: %w", err)
	}
	timestamp := time.UnixMilli(hdr.Timestamp).UTC()

	// Combine buffered data with rest of reader
	rc := internal.NewReadCounter(io.MultiReader(&buf, r))

	filename := ltx.FormatFilename(minTXID, maxTXID)
	key := c.ltxPath(level, filename)

	// Store timestamp in OSS metadata for accurate timestamp retrieval
	metadata := map[string]string{
		MetadataKeyTimestamp: timestamp.Format(time.RFC3339Nano),
	}

	// Use uploader for automatic multipart handling (files >5GB)
	result, err := c.uploader.UploadFrom(ctx, &oss.PutObjectRequest{
		Bucket:   oss.Ptr(c.Bucket),
		Key:      oss.Ptr(key),

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Validate the local LTX file with 'litestream ltx <file>' to confirm header integrity.
  2. If the local LTX state is corrupted, run 'litestream reset <db>' to clear it and let replication restart.
  3. Upgrade litestream so the writer and replica client use the same LTX format version.
  4. If this comes from custom code, ensure you pass the raw LTX stream (never a pre-opened/partially consumed reader).

Example fix

// before
f, _ := os.Open(corruptPath)
info, err := client.WriteLTXFile(ctx, key, metadata, f, size)
// after
if _, _, err := ltx.PeekHeader(f); err != nil {
    return fmt.Errorf("local ltx file corrupt, run litestream reset: %w", err)
}
f.Seek(0, io.SeekStart)
info, err := client.WriteLTXFile(ctx, key, metadata, f, size)
Defensive patterns

Strategy: validation

Validate before calling

// Verify the stream starts with a valid LTX header before uploading.
if _, _, err := ltx.PeekHeader(reader); err != nil {
    return fmt.Errorf("invalid ltx stream: %w", err)
}

Prevention

When it happens

Trigger: Passing a reader to WriteLTXFile whose first bytes are not a valid ltx.Header: truncated WAL-to-LTX conversion output, corrupted file on disk, or a caller (custom code/tests) feeding a non-LTX stream.

Common situations: Disk corruption or partial write of the local LTX file before upload; running a mismatched/older litestream version that produced an incompatible header; third-party tooling writing into the replica path.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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