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
- Verify the LTX file/stream at the source with `litestream ltx -level all` to confirm it is valid
- Check the writer feeding WriteLTXFile for truncation (ensure the producer finished writing before the reader is passed)
- Ensure litestream and ltx library versions match between producer and consumer
- 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
- Validate LTX files with `litestream ltx` before replication
- Ensure producers fully write streams before passing them
- Keep ltx library versions consistent across the pipeline
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.