{"record":{"id":"5d1c5dc22599d861","repo":"benbjohnson/litestream","slug":"close-decoder-w","errorCode":null,"errorMessage":"close decoder: %w","messagePattern":"close decoder: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"replica.go","lineNumber":996,"sourceCode":"\n\t\toff := int64(phdr.Pgno-1) * int64(pageSize)\n\t\tif _, err := f.WriteAt(data, off); err != nil {\n\t\t\treturn fmt.Errorf(\"write page %d: %w\", phdr.Pgno, err)\n\t\t}\n\t}\n\n\tif hdr.Commit > 0 {\n\t\tif err := f.Sync(); err != nil {\n\t\t\treturn fmt.Errorf(\"sync before truncate: %w\", err)\n\t\t}\n\t\tnewSize := int64(hdr.Commit) * int64(pageSize)\n\t\tif err := f.Truncate(newSize); err != nil {\n\t\t\treturn fmt.Errorf(\"truncate: %w\", err)\n\t\t}\n\t}\n\n\tif err := dec.Close(); err != nil {\n\t\treturn fmt.Errorf(\"close decoder: %w\", err)\n\t}\n\n\treturn f.Sync()\n}\n\n// fillFollowGap attempts to bridge a gap in level 0 files by searching\n// higher compaction levels for a file that covers the missing TXID range.\nfunc (r *Replica) fillFollowGap(ctx context.Context, f *os.File, afterTXID ltx.TXID, gapMinTXID ltx.TXID, pageSize uint32) (ltx.TXID, error) {\n\tcurrentTXID := afterTXID\n\n\tfor level := 1; level < SnapshotLevel; level++ {\n\t\titr, err := r.Client.LTXFiles(ctx, level, 0, false)\n\t\tif err != nil {\n\t\t\treturn currentTXID, fmt.Errorf(\"list level %d ltx files: %w\", level, err)\n\t\t}\n\t\tcloseLevel := func(retErr error) (ltx.TXID, error) {\n\t\t\tif closeErr := itr.Close(); closeErr != nil {\n\t\t\t\tcloseErr = fmt.Errorf(\"close level %d ltx iterator: %w\", level, closeErr)","sourceCodeStart":978,"sourceCodeEnd":1014,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/replica.go#L978-L1014","documentation":"After writing pages and truncating, applyLTXFile closes the LTX decoder. dec.Close() verifies the LTX file's trailing checksum over everything decoded; an error here means the transaction stream failed integrity verification even though the data decoded fine. Litestream must fail the apply because the source file cannot be trusted.","triggerScenarios":"dec.Close() returns non-nil: LTX checksum mismatch at end-of-stream, trailing bytes after the last page, or the reader rc returned fewer/more bytes than the header declared (e.g., storage returned a corrupted or partial object).","commonSituations":"Bit rot or partial multipart uploads in object storage, a proxy truncating the HTTP body, LTX file modified after creation (LTX files are immutable by contract), version mismatch between writer and reader.","solutions":["Treat the remote LTX file as corrupt: remove/quarantine it and let the replica re-upload","Run `litestream reset <db>` to clear local state and re-sync from a known-good snapshot","Inspect the storage object (etag/size) against what was uploaded to confirm corruption","Ensure nothing mutates LTX files after creation, including lifecycle/lambda processing"],"exampleFix":"// before: ignoring decoder close error\ndec.Close()\nreturn f.Sync()\n// after\nif err := dec.Close(); err != nil {\n    return fmt.Errorf(\"close decoder: %w\", err) // checksum verification failed\n}","handlingStrategy":"try-catch","validationCode":"// verify an LTX object end-to-end before applying\nfunc verifyLTX(ctx context.Context, rc io.ReadCloser) error {\n    dec := ltx.NewDecoder(rc)\n    if err := dec.DecodeHeader(); err != nil { return err }\n    buf := make([]byte, dec.Header().PageSize)\n    for {\n        var ph ltx.PageHeader\n        if err := dec.DecodePage(&ph, buf); err == io.EOF { break } else if err != nil { return err }\n    }\n    return dec.Close() // checksum verification happens here\n}","typeGuard":null,"tryCatchPattern":"if err := r.applyLTXFile(ctx, f, info, pageSize); err != nil {\n    if strings.Contains(err.Error(), \"close decoder\") {\n        // checksum verification failed: quarantine object, reset state\n        quarantineObject(info)\n        exec.Command(\"litestream\", \"reset\", dbPath).Run()\n    }\n    return err\n}","preventionTips":["Never write to or post-process LTX objects after upload","Compare uploaded object checksums (S3 etag/MD5) against local file checksums","Disable lifecycle rules that could alter replica objects","Keep writer and reader litestream versions compatible"],"tags":["ltx","checksum","integrity","replication"],"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"}