benbjohnson/litestream · error

open ltx file: %w

Error message

open ltx file: %w

What it means

Hydrator.Restore builds the hydration file by opening each LTX file listed in `infos` from the replica client and compacting them together. This error wraps a failure from client.OpenLTXFile while opening one of those files. It is a thin wrapper: the underlying cause (from the storage backend) is embedded via %w and is the real diagnostic.

Source

Thrown at vfs.go:776

}

// Restore restores the database from LTX files to the hydration file.
func (h *Hydrator) Restore(ctx context.Context, infos []*ltx.FileInfo) error {
	// Open all LTX files as readers
	rdrs := make([]io.Reader, 0, len(infos))
	defer func() {
		for _, rd := range rdrs {
			if closer, ok := rd.(io.Closer); ok {
				_ = closer.Close()
			}
		}
	}()

	for _, info := range infos {
		h.logger.Debug("opening ltx file for hydration", "level", info.Level, "min", info.MinTXID, "max", info.MaxTXID)
		rc, err := h.client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, 0)
		if err != nil {
			return fmt.Errorf("open ltx file: %w", err)
		}
		rdrs = append(rdrs, rc)
	}

	if len(rdrs) == 0 {
		return fmt.Errorf("no ltx files for hydration")
	}

	// Compact and decode using io.Pipe pattern
	pr, pw := io.Pipe()
	c, err := ltx.NewCompactor(pw, rdrs)
	if err != nil {
		return fmt.Errorf("new ltx compactor: %w", err)
	}
	c.HeaderFlags = ltx.HeaderFlagNoChecksum
	h.compactor = c

	go func() {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Unwrap the error (errors.Unwrap / %v print) to see the backend cause; fix storage credentials, network, or bucket config accordingly.
  2. Re-list LTX files immediately before Restore so `infos` reflects what the replica currently holds; skip or re-fetch missing files.
  3. If the replica state is corrupt or files were pruned, run `litestream reset` for the database and re-hydrate from the latest snapshot.
  4. Verify retention settings so the files needed for restore are not deleted mid-restore; raise retention or disable lifecycle cleanup on the bucket.

Example fix

// before
infos, _ := client.ListLTXFiles(ctx)
err := hydrator.Restore(ctx, infos)
// after
infos, err := client.ListLTXFiles(ctx)
if err != nil { return err }
infos = filterExisting(ctx, client, infos) // drop pruned files before opening
err = hydrator.Restore(ctx, infos)
Defensive patterns

Strategy: retry

Validate before calling

// check file presence before restore
for _, info := range infos {
    if _, err := client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, 0); err != nil {
        return fmt.Errorf("ltx %d-%d unavailable: %w", info.MinTXID, info.MaxTXID, err)
    }
}

Try / catch

if err := hydrator.Restore(ctx, infos); err != nil {
    var nf *fs.PathError
    if errors.As(err, &nf) { /* re-list and fall back to snapshot restore */ }
}

Prevention

When it happens

Trigger: Calling Hydrator.Restore(ctx, infos) where any info entry references an LTX file that OpenLTXFile cannot open on the replica — object deleted by retention/GC, wrong bucket/container credentials, network failure, or the FileInfo metadata being stale relative to what the replica actually holds.

Common situations: Replica retention removed a level-0 LTX file between the ListLTXFiles call and Restore; S3/GCS credentials rotated or revoked; restoring after `litestream reset` against a replica with mismatched TXIDs; offline development machine pointing at an unreachable object store.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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