benbjohnson/litestream · error

get v0.3.x time bounds: %w

Error message

get v0.3.x time bounds: %w

What it means

Wraps an error from TimeBoundsV3 while deciding whether a v0.3.x restore should be used instead of LTX. Litestream needs the min/max timestamps of v0.3.x backups to compare them against LTX coverage; failure here aborts format selection. The underlying cause (listing or metadata read failure against the replica client) is preserved.

Source

Thrown at replica.go:1404

	}

	if len(allSnapshots) == 0 {
		return nil, nil
	}

	// 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
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Inspect the wrapped cause; confirm the storage credentials allow listing on the replica bucket/prefix.
  2. Retry the restore if the underlying error is transient (network/5xx).
  3. Verify v0.3.x replica data integrity; re-sync or re-replicate if generations are incomplete.
  4. If v0.3.x backups are not needed, remove the legacy replica data so the LTX path is used.

Example fix

// before (caller sees wrapped error)
_, v3UpdatedAt, err := r.TimeBoundsV3(ctx, client)
// after
// pre-flight check by the operator:
// $ litestream replicas <db>   // confirm replica reachable
// then re-run restore
_, v3UpdatedAt, err := r.TimeBoundsV3(ctx, client)
if err != nil {
    return false, fmt.Errorf("get v0.3.x time bounds: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// verify replica reachable before restore
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := client.Generations(ctx); err != nil {
    return fmt.Errorf("replica unreachable: %w", err)
}

Try / catch

err := retry(3, backoff, func() error { _, err := restore(); return err })
if err != nil && strings.Contains(err.Error(), "get v0.3.x time bounds") {
    // transient backend failure; check IAM list permissions
}

Prevention

When it happens

Trigger: Restore triggers shouldUseV3Restore, which calls TimeBoundsV3; the client fails to enumerate or read v0.3.x backup metadata (network error, permission denied, corrupt generation listing).

Common situations: Object-store IAM policy lacks ListObjects on the replica prefix; v0.3.x-era replica directory contains partial/corrupt generation metadata; transient cloud outage during restore.

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/313841ca333482d4. Report an issue: GitHub.