benbjohnson/litestream · error
enforce snapshot retention: %w
Error message
enforce snapshot retention: %w
What it means
Store.EnforceSnapshotRetention failed while db.EnforceSnapshotRetention enumerated and pruned old snapshot-level LTX files on the replica. The method lists all snapshot LTX files via the replica client and deletes those older than the retention window; failures occur during listing or deletion. The underlying storage error is wrapped with %w.
Source
Thrown at store.go:839
db.Logger.InfoContext(ctx, "compaction complete",
"level", dstLevel,
slog.Group("txid",
"min", info.MinTXID.String(),
"max", info.MaxTXID.String(),
),
"size", info.Size,
)
return info, nil
}
// EnforceSnapshotRetention removes old snapshots by timestamp and then
// cleans up all lower levels based on minimum snapshot TXID.
func (s *Store) EnforceSnapshotRetention(ctx context.Context, db *DB) error {
// Enforce retention for the snapshot level.
minSnapshotTXID, err := db.EnforceSnapshotRetention(ctx, time.Now().Add(-s.SnapshotRetention))
if err != nil {
return fmt.Errorf("enforce snapshot retention: %w", err)
}
// We should also enforce retention for L0 on the same schedule as L1.
for _, lvl := range s.levels {
// Skip L0 since it is enforced on a more frequent basis.
if lvl.Level == 0 {
continue
}
if err := db.EnforceRetentionByTXID(ctx, lvl.Level, minSnapshotTXID); err != nil {
return fmt.Errorf("enforce L%d retention: %w", lvl.Level, err)
}
}
return nil
}
// ValidationResult holds the result of validating a replica's LTX files.View on GitHub (pinned to 4ed7a308f6)
Solutions
- Grant the replica credentials delete permissions on the bucket/prefix; check for object-lock or lifecycle policies that forbid deletion.
- Check the wrapped error (fetch vs delete phase) and fix the corresponding storage configuration.
- Verify SnapshotRetention is set to a sane value; an extremely negative/clock-skewed timestamp can produce unexpected prune sets.
- Retry after transient network errors; retention enforcement is idempotent.
- Disable retention only if a cloud lifecycle policy handles cleanup (RetentionEnabled=false).
Example fix
// before
{"storage": {"type": "s3", "bucket": "backups", "access-key-id": "..."}}
// after
{"storage": {"type": "s3", "bucket": "backups", "access-key-id": "...",
"comment": "IAM policy must allow s3:DeleteObject for retention pruning"}} Defensive patterns
Strategy: validation
Validate before calling
// preflight: verify delete permission on snapshot prefix
itr, err := client.LTXFiles(ctx, litestream.SnapshotLevel, 0, false)
if err != nil {
return fmt.Errorf("cannot list snapshots: %w", err)
}
itr.Close() Try / catch
if err := store.EnforceSnapshotRetention(ctx, db); err != nil {
if isPermissionError(err) {
log.Printf("retention blocked by storage permissions: %v", err)
// fix IAM policy, then retry
}
return err
} Prevention
- Grant s3:DeleteObject (or equivalent) to replica credentials
- Check object-lock/immutability policies before enabling retention
- Verify SnapshotRetention is sensible for your RPO requirements
- Disable retention only if cloud lifecycle policies handle cleanup
When it happens
Trigger: EnforceSnapshotRetention(ctx, db) (called from runOnce and monitorCompactionLevel) where Client.LTXFiles(ctx, SnapshotLevel, ...) fails or a delete operation on the storage backend fails.
Common situations: Storage credentials lacking delete permission (s3:DeleteObject), object-lock/immutability policies blocking deletes, network failure mid-pruning, or bucket misconfiguration.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- enforce L%d retention: %w
- fetch dst level info: %w
- fetch src level info: %w
- validate level %d for %s: %w
- snapshot retention must be greater than 0
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/cd0957b100438755.
Report an issue: GitHub.