{"record":{"id":"efabf166bf2e4fb1","repo":"benbjohnson/litestream","slug":"close-ltx-snapshot-encoder-w","errorCode":null,"errorMessage":"close ltx snapshot encoder: %w","messagePattern":"close ltx snapshot encoder: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db.go","lineNumber":2890,"sourceCode":"\t\t\tMinTXID:   1,\n\t\t\tMaxTXID:   pos.pos.TXID,\n\t\t\tTimestamp: time.Now().UnixMilli(),\n\t\t\tWALOffset: walOffset,\n\t\t\tWALSize:   walSize,\n\t\t\tWALSalt1:  rd.salt1,\n\t\t\tWALSalt2:  rd.salt2,\n\t\t}); err != nil {\n\t\t\tpw.CloseWithError(fmt.Errorf(\"encode ltx snapshot header: %w\", err))\n\t\t\treturn\n\t\t}\n\n\t\tif err := db.writeLTXFromDB(ctx, enc, walFile, commit, pageMap); err != nil {\n\t\t\tpw.CloseWithError(fmt.Errorf(\"write snapshot ltx: %w\", err))\n\t\t\treturn\n\t\t}\n\n\t\tif err := enc.Close(); err != nil {\n\t\t\tpw.CloseWithError(fmt.Errorf(\"close ltx snapshot encoder: %w\", err))\n\t\t\treturn\n\t\t}\n\t\t_ = pw.Close()\n\t}()\n\n\treturn &snapshotReadCloser{PipeReader: pr, pos: pos}, nil\n}\n\nfunc snapshotHeaderWALRange(maxOffset, frameSize int64) (offset, size int64) {\n\tif maxOffset <= WALHeaderSize || frameSize <= 0 {\n\t\treturn WALHeaderSize, 0\n\t}\n\toffset = max(maxOffset-frameSize, WALHeaderSize)\n\treturn offset, maxOffset - offset\n}\n\n// Compact performs a compaction of the LTX file at the previous level into dstLevel.\n// Returns metadata for the newly written compaction file. Returns ErrNoCompaction","sourceCodeStart":2872,"sourceCodeEnd":2908,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L2872-L2908","documentation":"Wraps a failure from enc.Close() finishing the LTX snapshot. LTX encoders write a final checksum/trailer on Close, so this fails if the trailing write into the pipe fails (consumer closed early) or the computed trailer is invalid (e.g. a prior encode step wrote bad data). The snapshot is incomplete when this fires and the whole stream is failed via pw.CloseWithError.","triggerScenarios":"enc.Close() returns an error at the end of snapshot generation: pipe reader already closed/cancelled by the consumer, or an internal encoder error writing the closing checksum record.","commonSituations":"Consumer treats the data as complete and closes the reader before EOF (must read to EOF, not just to the last known byte); context cancellation racing the final write; a corrupted stream from an earlier silent failure.","solutions":["Ensure the consumer reads the snapshot stream to EOF before closing — never close early after the last page","Check for an earlier CloseWithError on the pipe; the first error on the pipe is the root cause","Retry the snapshot with the consumer holding the reader open until EOF","If it persists with correct consumer behavior, validate the produced LTX with 'litestream ltx' to spot upstream corruption"],"exampleFix":"// before\nrc, _ := db.SnapshotReader(ctx, pos)\nio.CopyN(out, rc, knownSize) // closes early, producer fails on Close\n// after\nrc, _ := db.SnapshotReader(ctx, pos)\nio.Copy(out, rc) // read to EOF so enc.Close() succeeds\nrc.Close()","handlingStrategy":"validation","validationCode":"// consume the stream fully before closing\nrc, err := db.SnapshotReader(ctx, pos)\nif err != nil { return err }\nif _, err := io.Copy(out, rc); err != nil { return err } // to EOF\nif err := rc.Close(); err != nil { return err }","typeGuard":null,"tryCatchPattern":"err := streamSnapshot(ctx, db)\nif err != nil && strings.Contains(err.Error(), \"close ltx snapshot encoder\") {\n    // verify consumer read to EOF; check earlier pipe errors\n    return fmt.Errorf(\"snapshot not finalized: %w\", err)\n}","preventionTips":["Always read snapshot streams to EOF (io.Copy, not io.CopyN)","Validate finished snapshots with 'litestream ltx'","Treat the first pipe error as the root cause when diagnosing","Cancel contexts only when intentionally abandoning a snapshot"],"tags":["go","ltx","pipe","encoder","snapshot"],"backgroundTag":"broken-pipe","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"}