{"record":{"id":"f162323aec5f1180","repo":"benbjohnson/litestream","slug":"ltx-file-too-short-to-contain-a-page-index-d-byt","errorCode":null,"errorMessage":"ltx file too short to contain a page index: %d bytes","messagePattern":"ltx file too short to contain a page index: (.+?) bytes","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"replica_client.go","lineNumber":138,"sourceCode":"\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()\n\n\t// If we have read the full size of the page index, return the page index block as a reader.\n\tb, err := io.ReadAll(f)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read ltx page index: %w\", err)\n\t}\n\t// The replica may return fewer bytes than a page index footer occupies, for\n\t// example if the file is truncated or otherwise corrupt. Slicing the footer\n\t// off the end unchecked would index past the start of the buffer and panic.\n\tif len(b) < ltx.TrailerSize+8 {\n\t\treturn nil, fmt.Errorf(\"ltx file too short to contain a page index: %d bytes\", len(b))\n\t}\n\n\tsize := binary.BigEndian.Uint64(b[len(b)-ltx.TrailerSize-8:])\n\tif off := len(b) - int(size) - ltx.TrailerSize - 8; off > 0 {\n\t\treturn io.NopCloser(bytes.NewReader(b[off:])), nil\n\t}\n\n\t// Otherwise read the file from the start of the page index.\n\tf, err = client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, info.Size-ltx.TrailerSize-8-int64(size), 0)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"open ltx file: %w\", err)\n\t}\n\treturn f, nil\n}\n\n// FetchPage fetches and decodes a single page frame from an LTX file.\nfunc FetchPage(ctx context.Context, client ReplicaClient, level int, minTXID, maxTXID ltx.TXID, offset, size int64) (ltx.PageHeader, []byte, error) {\n\tf, err := client.OpenLTXFile(ctx, level, minTXID, maxTXID, offset, size)","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/replica_client.go#L120-L156","documentation":"Init validates that the bytes read from the replica are at least ltx.TrailerSize+8 long before slicing the trailer/page-index size off the end. If the replica returned a truncated or corrupt LTX file, this error is returned instead of panicking on an out-of-range slice. It means the remote LTX object does not contain a complete page index.","triggerScenarios":"Calling Init against an LTX file whose stored body is shorter than the trailer plus the 8-byte size field — e.g. the object was truncated by a failed upload or corrupt lifecycle/replication copy.","commonSituations":"Interrupted uploads leaving partial objects in S3; manual file tampering or bad sync tools copying LTX files; disk-full conditions on file:// replicas; provider bugs or mismatched multi-part upload aborts.","solutions":["Treat the remote LTX file as corrupt: re-replicate from the primary database or restore from an earlier TXID.","Run `litestream ltx` to inspect available LTX files and pick an intact generation.","If local state is corrupt relative to the replica, run `litestream reset` (or enable replica `auto-recover`).","Check the upload path that produced the object for interruption (partial multipart uploads, disk-full, crashes)."],"exampleFix":"// before: blindly restoring latest\nerr := db.Restore(ctx, litestream.RestoreConfig{})\n// after: detect corrupt LTX and fall back to an earlier backup\nif strings.Contains(err.Error(), \"too short to contain a page index\") {\n    log.Printf(\"corrupt LTX at txid %d; attempting earlier backup\", txid)\n    err = restoreAt(ctx, db, priorTxid)\n}","handlingStrategy":"fallback","validationCode":"if info, err := statLTX(ctx, client, level, minTXID, maxTXID); err != nil || info.Size < ltx.TrailerSize+8 { return fmt.Errorf(\"ltx object too small (%d bytes); likely truncated\", info.Size) }","typeGuard":null,"tryCatchPattern":"r, err := litestream.Init(ctx, client, level, minTXID, maxTXID)\nif err != nil && strings.Contains(err.Error(), \"too short to contain a page index\") {\n    log.Printf(\"corrupt/truncated LTX %d/%d; falling back to earlier backup\", level, minTXID)\n    return restoreFromEarlierBackup(ctx)\n}","preventionTips":["Ensure uploads are atomic (complete multipart uploads; temp-file + rename for file:// replicas).","Verify object size/ETag after upload matches the local LTX file.","Avoid manual copying/tampering with replica objects.","Alert on disk-full conditions for file-backed replicas."],"tags":["ltx","corruption","truncated-file","validation"],"backgroundTag":"checksum-mismatch","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}