benbjohnson/litestream · error

extract timestamp from LTX header: %w

Error message

extract timestamp from LTX header: %w

What it means

WriteLTXFile reads the LTX header from the incoming stream via io.TeeReader to extract the transaction timestamp. If ltx.PeekHeader fails (malformed, truncated, or unreadable header), the write is aborted and the underlying ltx error is wrapped with this message. It means the data being replicated is not a valid LTX stream at the very first bytes.

Source

Thrown at webdav/replica_client.go:249

//
// References:
//   - https://github.com/studio-b12/gowebdav/issues/35 (chunked encoding issues)
//   - https://github.com/nextcloud/server/issues/7995 (0-byte file bug)
//   - https://evertpot.com/260/ (WebDAV chunked encoding compatibility)
func (c *ReplicaClient) WriteLTXFile(ctx context.Context, level int, minTXID, maxTXID ltx.TXID, rd io.Reader) (info *ltx.FileInfo, err error) {
	client, err := c.init(ctx)
	if err != nil {
		return nil, err
	}

	filename := litestream.LTXFilePath(c.Path, level, minTXID, maxTXID)

	var buf bytes.Buffer
	teeReader := io.TeeReader(rd, &buf)

	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()

	// Stage to temporary file to get seekable reader with known size.
	// This ensures compatibility with all WebDAV servers and avoids the
	// unreliable chunked transfer encoding that causes silent data loss
	// on common configurations (Nginx+FastCGI, Lighttpd, Apache+FastCGI).
	tmpFile, err := os.CreateTemp("", "litestream-webdav-*.ltx")
	if err != nil {
		return nil, fmt.Errorf("webdav: cannot create temp file: %w", err)
	}
	defer func() {
		_ = tmpFile.Close()
		_ = os.Remove(tmpFile.Name())
	}()

	fullReader := io.MultiReader(&buf, rd)

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Verify the LTX file/stream at the source with `litestream ltx -level all` to confirm it is valid
  2. Check the writer feeding WriteLTXFile for truncation (ensure the producer finished writing before the reader is passed)
  3. Ensure litestream and ltx library versions match between producer and consumer
  4. Inspect any network path between producer and litestream for data-mutating proxies

Example fix

// before
hdr, _, err := ltx.PeekHeader(teeReader)
if err != nil { return nil, fmt.Errorf("extract timestamp from LTX header: %w", err) }
// after
if buf.Len() < ltx.HeaderSize { return nil, fmt.Errorf("extract timestamp from LTX header: stream too short (%d bytes)", buf.Len()) }
hdr, _, err := ltx.PeekHeader(teeReader)
if err != nil { return nil, fmt.Errorf("extract timestamp from LTX header: %w", err) }
Defensive patterns

Strategy: validation

Validate before calling

data, _ := io.ReadAll(io.LimitReader(rd, ltx.HeaderSize))
if len(data) < ltx.HeaderSize { return errors.New("stream too short to be a valid LTX file") }
if _, _, err := ltx.PeekHeader(bytes.NewReader(data)); err != nil { return fmt.Errorf("invalid LTX header: %w", err) }
rd = io.MultiReader(bytes.NewReader(data), rd)

Prevention

When it happens

Trigger: Calling WriteLTXFile with a reader whose first bytes are not a valid LTX header: empty stream, truncated WAL->LTX conversion output, or a corrupted source stream.

Common situations: Disk corruption producing truncated LTX data, a proxy/middleware intercepting and altering the stream, version mismatch where an older litestream produced headers the current ltx decoder rejects.

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/f16c79107f10ce92. Report an issue: GitHub.