{"record":{"id":"f7389ab65c443435","repo":"benbjohnson/litestream","slug":"peek-header-w","errorCode":null,"errorMessage":"peek header: %w","messagePattern":"peek header: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"replica_client.go","lineNumber":109,"sourceCode":"\trc, err := fetchPageIndexData(ctx, client, info)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rc.Close()\n\n\treturn ltx.DecodePageIndex(bufio.NewReader(rc), info.Level, info.MinTXID, info.MaxTXID)\n}\n\n// FetchLTXHeader reads & returns the LTX header for the given file info.\nfunc FetchLTXHeader(ctx context.Context, client ReplicaClient, info *ltx.FileInfo) (ltx.Header, error) {\n\trc, err := client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, ltx.HeaderSize)\n\tif err != nil {\n\t\treturn ltx.Header{}, fmt.Errorf(\"open ltx file: %w\", err)\n\t}\n\tdefer rc.Close()\n\thdr, _, err := ltx.PeekHeader(rc)\n\tif err != nil {\n\t\treturn ltx.Header{}, fmt.Errorf(\"peek header: %w\", err)\n\t}\n\treturn hdr, nil\n}\n\n// fetchPageIndexData fetches a chunk of the end of the file to get the page index.\n// If the fetch was smaller than the actual page index, another call is made to fetch the rest.\nfunc fetchPageIndexData(ctx context.Context, client ReplicaClient, info *ltx.FileInfo) (io.ReadCloser, error) {\n\t// Fetch the end of the file to get the page index.\n\toffset := info.Size - DefaultEstimatedPageIndexSize\n\tif offset < 0 {\n\t\toffset = 0\n\t}\n\n\tf, err := client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, offset, 0)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"open ltx file: %w\", err)\n\t}\n\tdefer f.Close()","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/replica_client.go#L91-L127","documentation":"After successfully opening the LTX file, FetchLTXHeader calls ltx.PeekHeader to read and decode the LTX header bytes. This error means the header could not be read or decoded: the file is empty, truncated, or its first bytes are not a valid LTX header — i.e. the stored object is corrupt or not an LTX file at all.","triggerScenarios":"client.OpenLTXFile succeeded but PeekHeader fails: zero-byte or truncated object in storage (aborted upload), object overwritten with non-LTX data (wrong content written to the replica prefix), network read cut off mid-header for remote stores, or a corrupt local LTX file.","commonSituations":"Interrupted uploads leaving partial objects in the bucket; someone/something wrote other files into the replica path; disk corruption on the replica volume; restoring from a replica where a compaction crash left a truncated file.","solutions":["Identify and delete the corrupt LTX object, then re-restore from an intact generation, or run 'litestream reset' to clear corrupted local LTX state","Verify the replica storage contents — the object should start with the LTX magic bytes; an empty or foreign file must be removed","If the local WAL/replica state is suspect, use 'litestream restore' from a known-good replica copy","Check for processes writing non-LTX files into the replica path and stop them"],"exampleFix":"# before: repeatedly retrying restore against a corrupt object\nlitestream restore -o my.db mydb.db   # fails with peek header\n# after: clear corrupted local state and re-restore\nlitestream reset mydb.db\nlitestream restore -o my.db mydb.db","handlingStrategy":"validation","validationCode":"// Verify the stored object is a non-empty LTX file before decoding\nrc, err := client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, ltx.HeaderSize)\nif err == nil {\n    magic := make([]byte, 4)\n    io.ReadFull(rc, magic)\n    rc.Close()\n    if !bytes.Equal(magic, []byte(\"LTX1\")) {\n        return fmt.Errorf(\"object %s is not a valid LTX file\", info.MaxTXID)\n    }\n}","typeGuard":"func looksLikeLTX(ctx context.Context, c ReplicaClient, info *ltx.FileInfo) bool {\n    rc, err := c.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, ltx.HeaderSize)\n    if err != nil { return false }\n    defer rc.Close()\n    hdr, _, err := ltx.PeekHeader(rc)\n    return err == nil && hdr.IsValid()\n}","tryCatchPattern":"hdr, err := FetchLTXHeader(ctx, client, info)\nif err != nil {\n    if strings.Contains(err.Error(), \"peek header\") {\n        // corrupt/truncated object: skip it and choose another restore candidate\n    }\n    return err\n}","preventionTips":["Skip zero-length or non-LTX objects when choosing restore candidates","Never write non-LTX files into the replica storage prefix","Ensure uploads complete before publishing (atomic object creation where supported)","Use 'litestream reset' to clear corrupted local LTX state instead of retrying blindly"],"tags":["ltx","corruption","header","restore"],"backgroundTag":"checksum-mismatch","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}