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

  1. Re-list the replica (litestream ltx / ValidateLevel) to get current file info — the referenced file may have been removed by retention or compaction
  2. Verify storage credentials allow object reads (e.g. s3:GetObject) and the bucket/endpoint config is correct
  3. If local state is inconsistent, run 'litestream reset' for the database or perform a fresh 'litestream restore'
  4. 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

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


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/757b9d74997f7b22. Report an issue: GitHub.