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.

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)

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.