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
- Check the wrapped cause and verify the LTX replica path in the config matches the actual storage layout.
- Confirm object-store lifecycle rules have not deleted LTX files under generations/.
- Retry on transient storage errors; use litestream reset only if local LTX state is corrupted (note it clears local LTX state).
- 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
- Keep replica paths in litestream.yml in sync with bucket renames/migrations.
- Audit object-store lifecycle rules so they never delete LTX files mid-chain.
- Disable built-in retention only when a cloud lifecycle policy handles cleanup.
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.