benbjohnson/litestream · error

no ltx files for hydration

Error message

no ltx files for hydration

What it means

Hydrator.Restore refuses to run when, after iterating the provided FileInfo list, zero readers were opened (empty `infos` yields no LTX readers). Restoring a database from nothing would produce an empty/corrupt hydration file, so the hydrator fails fast with this sentinel-style error.

Source

Thrown at vfs.go:782

	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() {
		_ = pw.CloseWithError(c.Compact(ctx))
	}()

	h.mu.Lock()
	defer h.mu.Unlock()

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check that the replica actually contains LTX files (`litestream ltx -level all` or ListLTXFiles) and that the configured path/prefix matches the database.
  2. Re-run restore with infos covering a valid TXID range (start from 1 / the oldest retained snapshot) instead of an offset past the newest file.
  3. If the replica is genuinely empty, trigger a new snapshot/backup before attempting hydration.
  4. Guard callers: only invoke Restore when len(infos) > 0 and fall back to a full replication sync otherwise.

Example fix

// before
if err := hydrator.Restore(ctx, infos); err != nil { return err }
// after
if len(infos) == 0 {
    return fmt.Errorf("no ltx files on replica; run a snapshot first")
}
if err := hydrator.Restore(ctx, infos); err != nil { return err }
Defensive patterns

Strategy: validation

Validate before calling

if len(infos) == 0 {
    return fmt.Errorf("no ltx files available; trigger a snapshot before restore")
}

Try / catch

if err := hydrator.Restore(ctx, infos); err != nil {
    if strings.Contains(err.Error(), "no ltx files") {
        // fall back to forcing a fresh replication snapshot
    }
}

Prevention

When it happens

Trigger: Calling Hydrator.Restore(ctx, nil) or Restore with an empty []*ltx.FileInfo slice — e.g. the caller listed LTX files at a TXID beyond everything the replica retains, or the replica bucket is empty.

Common situations: Fresh database whose first snapshot has not replicated yet; restore-from offset newer than the latest replica TXID; bucket pointed at the wrong path/prefix so listing returns nothing; retention wiped all files.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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