{"record":{"id":"cd0957b100438755","repo":"benbjohnson/litestream","slug":"enforce-snapshot-retention-w","errorCode":null,"errorMessage":"enforce snapshot retention: %w","messagePattern":"enforce snapshot retention: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"store.go","lineNumber":839,"sourceCode":"\tdb.Logger.InfoContext(ctx, \"compaction complete\",\n\t\t\"level\", dstLevel,\n\t\tslog.Group(\"txid\",\n\t\t\t\"min\", info.MinTXID.String(),\n\t\t\t\"max\", info.MaxTXID.String(),\n\t\t),\n\t\t\"size\", info.Size,\n\t)\n\n\treturn info, nil\n}\n\n// EnforceSnapshotRetention removes old snapshots by timestamp and then\n// cleans up all lower levels based on minimum snapshot TXID.\nfunc (s *Store) EnforceSnapshotRetention(ctx context.Context, db *DB) error {\n\t// Enforce retention for the snapshot level.\n\tminSnapshotTXID, err := db.EnforceSnapshotRetention(ctx, time.Now().Add(-s.SnapshotRetention))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"enforce snapshot retention: %w\", err)\n\t}\n\n\t// We should also enforce retention for L0 on the same schedule as L1.\n\tfor _, lvl := range s.levels {\n\t\t// Skip L0 since it is enforced on a more frequent basis.\n\t\tif lvl.Level == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := db.EnforceRetentionByTXID(ctx, lvl.Level, minSnapshotTXID); err != nil {\n\t\t\treturn fmt.Errorf(\"enforce L%d retention: %w\", lvl.Level, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ValidationResult holds the result of validating a replica's LTX files.","sourceCodeStart":821,"sourceCodeEnd":857,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/store.go#L821-L857","documentation":"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.","triggerScenarios":"EnforceSnapshotRetention(ctx, db) (called from runOnce and monitorCompactionLevel) where Client.LTXFiles(ctx, SnapshotLevel, ...) fails or a delete operation on the storage backend fails.","commonSituations":"Storage credentials lacking delete permission (s3:DeleteObject), object-lock/immutability policies blocking deletes, network failure mid-pruning, or bucket misconfiguration.","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)."],"exampleFix":"// before\n{\"storage\": {\"type\": \"s3\", \"bucket\": \"backups\", \"access-key-id\": \"...\"}}\n// after\n{\"storage\": {\"type\": \"s3\", \"bucket\": \"backups\", \"access-key-id\": \"...\", \n  \"comment\": \"IAM policy must allow s3:DeleteObject for retention pruning\"}}","handlingStrategy":"validation","validationCode":"// preflight: verify delete permission on snapshot prefix\nitr, err := client.LTXFiles(ctx, litestream.SnapshotLevel, 0, false)\nif err != nil {\n    return fmt.Errorf(\"cannot list snapshots: %w\", err)\n}\nitr.Close()","typeGuard":null,"tryCatchPattern":"if err := store.EnforceSnapshotRetention(ctx, db); err != nil {\n    if isPermissionError(err) {\n        log.Printf(\"retention blocked by storage permissions: %v\", err)\n        // fix IAM policy, then retry\n    }\n    return err\n}","preventionTips":["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"],"tags":["go","litestream","retention","storage"],"backgroundTag":"permission-denied","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}