benbjohnson/litestream · error

get LTX time bounds: %w

Error message

get LTX time bounds: %w

What it means

Wraps an error from TimeBounds while reading LTX-format time bounds during the v0.3.x vs LTX restore decision. Both formats' bounds are needed to pick the better restore source; failing to read the LTX bounds aborts the restore. The cause is the underlying client listing/manifest error.

Source

Thrown at replica.go:1410

	// Sort by CreatedAt for timestamp-based selection.
	sortSnapshotsV3ByCreatedAt(allSnapshots)

	return findBestSnapshotV3(allSnapshots, timestamp), nil
}

// shouldUseV3Restore determines whether to use v0.3.x restore instead of LTX.
// Returns true if v0.3.x has a better backup for the given options.
func (r *Replica) shouldUseV3Restore(ctx context.Context, client ReplicaClientV3, timestamp time.Time) (bool, error) {
	// Get v0.3.x time bounds.
	_, v3UpdatedAt, err := r.TimeBoundsV3(ctx, client)
	if err != nil {
		return false, fmt.Errorf("get v0.3.x time bounds: %w", err)
	}

	// Get LTX time bounds.
	_, ltxUpdatedAt, err := r.TimeBounds(ctx)
	if err != nil {
		return false, fmt.Errorf("get LTX time bounds: %w", err)
	}

	// If no v0.3.x backups exist, use LTX.
	if v3UpdatedAt.IsZero() {
		return false, nil
	}

	// If no LTX backups exist, use v0.3.x.
	if ltxUpdatedAt.IsZero() {
		r.Logger().Debug("using v0.3.x restore (no LTX backups)")
		return true, nil
	}

	// Both formats have backups - compare based on timestamp or latest.
	if !timestamp.IsZero() {
		// With timestamp: use format with best snapshot before timestamp.
		v3Snapshot, err := r.findBestV3SnapshotForTimestamp(ctx, client, timestamp)
		if err != nil {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check the wrapped cause and verify the LTX replica path in the config matches the actual storage layout.
  2. Confirm object-store lifecycle rules have not deleted LTX files under generations/.
  3. Retry on transient storage errors; use litestream reset only if local LTX state is corrupted (note it clears local LTX state).
  4. If LTX data is gone intentionally, restore from the v0.3.x replica with an explicit restore target.

Example fix

// before
_, ltxUpdatedAt, err := r.TimeBounds(ctx)
if err != nil {
    return false, fmt.Errorf("get LTX time bounds: %w", err)
}
// after
// verify config points at the right replica path, then:
_, ltxUpdatedAt, err := r.TimeBounds(ctx)
if err != nil {
    return false, fmt.Errorf("get LTX time bounds: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

// confirm LTX replica path layout matches config before restore
// storage bucket must contain generations/<gen>/... for the configured path
if replicaPath == "" || !strings.HasPrefix(replicaPath, expectedPrefix) {
    return fmt.Errorf("replica path mismatch")
}

Try / catch

if err := restore(...); err != nil {
    if strings.Contains(err.Error(), "get LTX time bounds") {
        // check config replica path + lifecycle rules before retrying
    }
    return err
}

Prevention

When it happens

Trigger: Restore -> shouldUseV3Restore -> TimeBounds(ctx) fails because the LTX replica data cannot be listed: missing/renamed replica path, storage outage, or corrupted LTX directory structure.

Common situations: Replica path misconfigured in the litestream.yml after a bucket migration; LTX files deleted by a lifecycle policy; object storage returning errors mid-listing.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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