benbjohnson/litestream · critical

TODO(ltx): Re-implement after multi-level compaction

Error message

TODO(ltx): Re-implement after multi-level compaction

What it means

Replica.EnforceRetention is currently a stub: multi-level compaction work in the LTX rewrite left retention enforcement unimplemented, so the method panics with a TODO marker. The original retention logic (listing/removing old snapshots and WAL files) is commented out. Any caller invoking it on this version of the code will crash.

Solutions

  1. Stop calling EnforceRetention until the TODO is implemented; manage retention on the replica backend (e.g. S3 lifecycle rules) instead
  2. Pin to the last version before the LTX rewrite if you need programmatic EnforceRetention
  3. Check store.go / current config options for replacement retention settings; upgrade litestream if a later release re-implements retention

Example fix

// before
if err := replica.EnforceRetention(ctx); err != nil { ... } // panics
// after: rely on storage lifecycle policy instead
// e.g. S3 bucket lifecycle rule: expire *.ltx older than 24h
Defensive patterns

Strategy: validation

Validate before calling

// Go: guard the call site against the stub implementation
// TODO was flagged in source: replica.go EnforceRetention panics on this version.
if retentionEnforcedExplicitly {
    return errors.New("EnforceRetention is unimplemented in this litestream version; use storage lifecycle rules")
}

Try / catch

// panic-based stub: recover defensively if you must call it
func safeEnforceRetention(ctx context.Context, r *litestream.Replica) (err error) {
    defer func() { if p := recover(); p != nil { err = fmt.Errorf("EnforceRetention unavailable: %v", p) } }()
    return r.EnforceRetention(ctx)
}

Prevention

When it happens

Trigger: Any call to (*litestream.Replica).EnforceRetention(ctx) on this source revision — e.g. external code that used the library API to enforce retention, or store loops still wired to it — hits the panic unconditionally.

Common situations: Upgrading to the LTX-era version of litestream while code (or a fork/monitor) still calls EnforceRetention directly; expecting retention to run automatically and instead getting a crash; depending on the removed retention behavior after a version change.

Related errors


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

Appendix: source

Thrown at replica.go:319

// Pos returns the current replicated position.
// Returns a zero value if the current position cannot be determined.
func (r *Replica) Pos() ltx.Pos {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.pos
}

// SetPos sets the current replicated position.
func (r *Replica) SetPos(pos ltx.Pos) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.pos = pos
}

// EnforceRetention forces a new snapshot once the retention interval has passed.
// Older snapshots and WAL files are then removed.
func (r *Replica) EnforceRetention(ctx context.Context) (err error) {
	panic("TODO(ltx): Re-implement after multi-level compaction")

	/*
		// Obtain list of snapshots that are within the retention period.
		snapshots, err := r.Snapshots(ctx)
		if err != nil {
			return fmt.Errorf("snapshots: %w", err)
		}
		retained := FilterSnapshotsAfter(snapshots, time.Now().Add(-r.Retention))

		// If no retained snapshots exist, create a new snapshot.
		if len(retained) == 0 {
			snapshot, err := r.Snapshot(ctx)
			if err != nil {
				return fmt.Errorf("snapshot: %w", err)
			}
			retained = append(retained, snapshot)
		}

View on GitHub (pinned to 4ed7a308f6)