benbjohnson/litestream · error

fetch page index: %w

Error message

fetch page index: %w

What it means

During ResetTime/rebuildIndex, the VFS iterates the restore plan and calls FetchPageIndex to download each LTX file's page index from the replica. Any failure reading or parsing a page index (network error, missing object, corrupt LTX) is wrapped as "fetch page index: %w". The rebuild is aborted so no partial index is swapped in.

Source

Thrown at vfs.go:1345

	for _, info := range infos {
		if info.Level == level && info.MaxTXID > maxTXID {
			maxTXID = info.MaxTXID
		}
	}
	return maxTXID
}

// buildIndexMap constructs a lookup of pgno to LTX file offsets.
func (f *VFSFile) buildIndexMap(ctx context.Context, infos []*ltx.FileInfo) (map[uint32]ltx.PageIndexElem, error) {
	index := make(map[uint32]ltx.PageIndexElem)
	var commit uint32
	for _, info := range infos {
		f.logger.Debug("opening page index", "level", info.Level, "min", info.MinTXID, "max", info.MaxTXID)

		// Read page index.
		idx, err := FetchPageIndex(ctx, f.client, info)
		if err != nil {
			return nil, fmt.Errorf("fetch page index: %w", err)
		}

		// Replace pages in overall index with new pages.
		for k, v := range idx {
			f.logger.Debug("adding page index", "page", k, "elem", v)
			index[k] = v
		}
		hdr, err := FetchLTXHeader(ctx, f.client, info)
		if err != nil {
			return nil, fmt.Errorf("fetch header: %w", err)
		}
		commit = hdr.Commit
	}

	f.mu.Lock()
	f.commit = commit
	f.mu.Unlock()

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check the wrapped cause (%w) to distinguish network errors from decode errors.
  2. Retry the operation — transient network/object-store errors often resolve on retry.
  3. Verify credentials/permissions allow reading objects from the replica bucket.
  4. Remove or re-replicate the corrupted LTX file, or run `litestream reset` to clear bad local state.
Defensive patterns

Strategy: retry

Validate before calling

// precheck: confirm the object is reachable before rebuild
for _, info := range infos {
    if _, err := client.Open(ctx, fmt.Sprintf("%08x.ltx", info.MinTXID)); err != nil {
        return fmt.Errorf("LTX %d unreachable: %w", info.MinTXID, err)
    }
}

Try / catch

if err := file.ResetTime(ctx); err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) || isRetryable(err) {
        err = retry.Do(func() error { return file.ResetTime(ctx) }, retry.Attempts(3))
    }
}

Prevention

When it happens

Trigger: Calling ResetTime (or triggering a time-travel rebuild) when a listed LTX file cannot be downloaded from the replica client — network outage, deleted object, expired credentials, or corrupted LTX file whose index cannot be decoded.

Common situations: S3/GCS/Azure transient network failures or throttling during rebuild; an LTX file deleted by lifecycle rules between CalcRestorePlan and FetchPageIndex; IAM credentials lacking GetObject on the bucket; partially-uploaded/corrupt LTX file.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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