benbjohnson/litestream · error
open ltx file: %w
Error message
open ltx file: %w
What it means
FetchLTXHeader opens a specific LTX file on the replica (client.OpenLTXFile, restricted to the header bytes) and this error wraps the open failure. Per the RAISED-IN note, it surfaces through Init and related flows (restoreIfNotExists, LTXFiles, WriteLTXFile, OpenLTXFile, DeleteLTXFiles, DeleteAll), meaning an LTX file recorded in the listing could not actually be opened for reading.
Source
Thrown at replica_client.go:104
// DefaultEstimatedPageIndexSize is size that is first fetched when fetching the page index.
// If the fetch was smaller than the actual page index, another call is made to fetch the rest.
const DefaultEstimatedPageIndexSize = 32 * 1024 // 32KB
func FetchPageIndex(ctx context.Context, client ReplicaClient, info *ltx.FileInfo) (map[uint32]ltx.PageIndexElem, error) {
rc, err := fetchPageIndexData(ctx, client, info)
if err != nil {
return nil, err
}
defer rc.Close()
return ltx.DecodePageIndex(bufio.NewReader(rc), info.Level, info.MinTXID, info.MaxTXID)
}
// FetchLTXHeader reads & returns the LTX header for the given file info.
func FetchLTXHeader(ctx context.Context, client ReplicaClient, info *ltx.FileInfo) (ltx.Header, error) {
rc, err := client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, ltx.HeaderSize)
if err != nil {
return ltx.Header{}, fmt.Errorf("open ltx file: %w", err)
}
defer rc.Close()
hdr, _, err := ltx.PeekHeader(rc)
if err != nil {
return ltx.Header{}, fmt.Errorf("peek header: %w", err)
}
return hdr, nil
}
// fetchPageIndexData fetches a chunk of the end of the file to get the page index.
// If the fetch was smaller than the actual page index, another call is made to fetch the rest.
func fetchPageIndexData(ctx context.Context, client ReplicaClient, info *ltx.FileInfo) (io.ReadCloser, error) {
// Fetch the end of the file to get the page index.
offset := info.Size - DefaultEstimatedPageIndexSize
if offset < 0 {
offset = 0
}
View on GitHub (pinned to 4ed7a308f6)
Solutions
- Re-list the replica (litestream ltx / ValidateLevel) to get current file info — the referenced file may have been removed by retention or compaction
- Verify storage credentials allow object reads (e.g. s3:GetObject) and the bucket/endpoint config is correct
- If local state is inconsistent, run 'litestream reset' for the database or perform a fresh 'litestream restore'
- Check for concurrent processes deleting from the same replica (single replica per database rule)
Example fix
// before: opening a FileInfo captured long ago rc, err := client.OpenLTXFile(ctx, staleInfo.Level, staleInfo.MinTXID, staleInfo.MaxTXID, 0, ltx.HeaderSize) // after: re-fetch fresh file info right before opening info, err := findFile(ctx, client, level, txid) // fresh listing rc, err := client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, ltx.HeaderSize)
Defensive patterns
Strategy: retry
Validate before calling
// Re-list to confirm the file still exists before opening
itr, err := client.LTXFiles(ctx, info.Level, info.MinTXID, false)
if err == nil {
var found bool
for itr.Next() { if itr.Item().MaxTXID == info.MaxTXID { found = true; break } }
if !found { return fmt.Errorf("LTX file %s no longer present", info.MaxTXID) }
} Type guard
func fileStillListed(ctx context.Context, c ReplicaClient, info *ltx.FileInfo) bool {
itr, err := c.LTXFiles(ctx, info.Level, info.MinTXID, false)
if err != nil { return false }
defer itr.Close()
for itr.Next() {
it := itr.Item()
if it.MinTXID == info.MinTXID && it.MaxTXID == info.MaxTXID { return true }
}
return false
} Try / catch
hdr, err := FetchLTXHeader(ctx, client, info)
if err != nil {
if errors.Is(err, os.ErrNotExist) || isNotFoundErr(err) {
// re-list and pick a fresher restore candidate
}
return err
} Prevention
- Do not run retention cleanup concurrently with restore/validation
- Always work from a freshly fetched file listing, not cached FileInfo
- Verify storage read permissions (e.g. s3:GetObject) in IAM policy
- Use 'litestream reset' when local state references missing objects
When it happens
Trigger: OpenLTXFile called with (level, MinTXID, MaxTXID) that does not resolve to a stored object: the file was deleted by retention/compaction between listing and open, the object name is wrong, storage credentials lack read access, or the object is missing in the bucket/container.
Common situations: A retention job or manual cleanup deleted LTX files while a restore/validation was in progress; replica URL points to a bucket where compaction removed the referenced generation; misconfigured endpoint causing 404-style open failures; IAM policy missing s3:GetObject.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- no snapshots available
- no replica client configured
- restore from backup: %w
- list generations: %w
- list snapshots for generation %s: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/757b9d74997f7b22.
Report an issue: GitHub.