benbjohnson/litestream · error

poll L0

Error message

poll L0: %w

What it means

Wrapper around a failure in pollLevel(level=0) while the VFS background poller fetches new L0 (transaction-level) LTX files from the replica storage client. The wrapped error is the underlying ReplicaClient failure (listing or opening LTX files).

Solutions

  1. Read the wrapped error to identify the storage client failure
  2. Verify replica credentials and bucket/container configuration
  3. Check network connectivity / storage endpoint reachability from the host
  4. Retry after transient failures — the poller retries on the next PollInterval tick; use litestream reset if local LTX state is corrupted
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight storage check before opening the VFS
iter, err := client.LTXFiles(ctx, 0, 1, false)
if err != nil {
    return fmt.Errorf("replica storage unavailable: %w", err)
}
iter.Close()

Try / catch

if err := pollOnce(ctx); err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) || isRetryablePageError(err) {
        time.Sleep(backoff)
        return pollOnce(ctx)
    }
    return err
}

Prevention

When it happens

Trigger: Periodic pollReplicaClient call; client.LTXFiles(ctx, 0, ...) fails due to network errors, credentials expiry, bucket not found, or storage API errors.

Common situations: S3/blob storage outages or throttling during polling; revoked IAM credentials; misconfigured replica client after environment changes; DNS/network failures in containers.

Related errors


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

Appendix: source

Thrown at vfs.go:2609

// pollReplicaClient fetches new LTX files from the replica client and updates
// the page index & the current position.
func (f *VFSFile) pollReplicaClient(ctx context.Context) error {
	pos := f.Pos()
	f.logger.Debug("polling replica client", "txid", pos.TXID.String())

	combined := make(map[uint32]ltx.PageIndexElem)

	f.mu.Lock()
	baseCommit := f.commit
	maxTXID1Snapshot := f.maxTXID1
	f.mu.Unlock()

	newCommit := baseCommit
	replaceIndex := false

	maxTXID0, idx0, commit0, replace0, err := f.pollLevel(ctx, 0, pos.TXID, baseCommit)
	if err != nil {
		return fmt.Errorf("poll L0: %w", err)
	}
	if replace0 {
		replaceIndex = true
		baseCommit = commit0
		newCommit = commit0
		combined = idx0
	} else {
		if len(idx0) > 0 {
			baseCommit = commit0
		}
		for k, v := range idx0 {
			combined[k] = v
		}
		if commit0 > newCommit {
			newCommit = commit0
		}
	}

View on GitHub (pinned to 4ed7a308f6)