kopia/kopia · error
unable to schedule delayed cleanup of blobs
Error message
unable to schedule delayed cleanup of blobs
What it means
ManagerV0.deleteOldBlobs fails to schedule deferred deletion of compaction-log blobs. After deleting old index blobs, it must write a cleanup-entry blob recording which compaction logs to delete later; if delayCleanupBlobs returns an error, this wrapper is returned to registerCompaction and thus to Compact. Compaction itself succeeded, but garbage-collection bookkeeping failed, so old compaction logs linger and cleanup is not scheduled.
Solutions
- Inspect the wrapped inner error for the actual storage failure (credentials, connectivity, quota) and fix it.
- Retry Compact(); delayed-cleanup scheduling is idempotent-safe to retry since it only writes a cleanup entry blob.
- Verify the storage destination is writable and not in a read-only or expired state.
- Check server clock sanity: cleanup schedule time comes from the latest blob's server timestamp.
Example fix
// before
if err := m.delayCleanupBlobs(ctx, compactionLogBlobsToDelayCleanup, latestBlob.Timestamp); err != nil {
return errors.Wrap(err, "unable to schedule delayed cleanup of blobs")
}
// after
if err := m.delayCleanupBlobs(ctx, compactionLogBlobsToDelayCleanup, latestBlob.Timestamp); err != nil {
m.log.Errorw("delayed cleanup scheduling failed; will retry on next compaction", "err", err)
return errors.Wrap(err, "unable to schedule delayed cleanup of blobs")
} Defensive patterns
Strategy: retry
Validate before calling
// Go: check storage writability before Compact
if err := storageTest(ctx, blobStore); err != nil {
return fmt.Errorf("blob storage not writable: %w", err)
} Try / catch
err := mgr.Compact(ctx); if err != nil && strings.Contains(err.Error(), "unable to schedule delayed cleanup") { // retry after storage recovers
retry.Do(func() error { return mgr.Compact(ctx) }, retry.Attempts(3))
} Prevention
- Run compaction during stable network/credential windows
- Monitor blob-store write error rates and alert before compaction jobs
- Keep storage credentials rotated with overlap so they never expire mid-job
When it happens
Trigger: Calling Compact() on an index blob manager when the underlying storage write in delayCleanupBlobs (EncryptAndWriteBlob with V0CleanupBlobPrefix) fails, or JSON marshaling of the cleanupEntry fails; the inner error is wrapped with this message by deleteOldBlobs.
Common situations: Blob storage backend outage or throttling during compaction (S3/Blob Storage 5xx), expired or revoked cloud credentials, network partition between the kopia client and the blob store, or a read-only/misconfigured storage destination.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- error deleting old index blobs
- error listing compaction log blobs
- error loading single-epoch compactions
- unable to cleanup log
- unable to compact blobs
AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07).
Data as JSON: /api/errors/3ac944ec8dd4689f.
Report an issue: GitHub.
Appendix: source
Thrown at repo/content/indexblob/index_blob_manager_v0.go:324
logparam.Time("compactionLogServerTimeCutoff", compactionLogServerTimeCutoff))
compactionBlobEntries, err := m.getCompactionLogEntries(ctx, compactionBlobs)
if err != nil {
return errors.Wrap(err, "unable to get compaction log entries")
}
indexBlobsToDelete := m.findIndexBlobsToDelete(ctx, latestBlob.Timestamp, compactionBlobEntries, maxEventualConsistencySettleTime)
// note that we must always delete index blobs first before compaction logs
// otherwise we may inadvertently resurrect an index blob that should have been removed.
if err := m.deleteBlobsFromStorageAndCache(ctx, indexBlobsToDelete); err != nil {
return errors.Wrap(err, "unable to delete compaction logs")
}
compactionLogBlobsToDelayCleanup := m.findCompactionLogBlobsToDelayCleanup(ctx, compactionBlobs)
if err := m.delayCleanupBlobs(ctx, compactionLogBlobsToDelayCleanup, latestBlob.Timestamp); err != nil {
return errors.Wrap(err, "unable to schedule delayed cleanup of blobs")
}
return nil
}
func (m *ManagerV0) findIndexBlobsToDelete(ctx context.Context, latestServerBlobTime time.Time, entries map[blob.ID]*compactionLogEntry, maxEventualConsistencySettleTime time.Duration) []blob.ID {
tmp := map[blob.ID]bool{}
for _, cl := range entries {
// are the input index blobs in this compaction eligible for deletion?
if age := latestServerBlobTime.Sub(cl.metadata.Timestamp); age < maxEventualConsistencySettleTime {
contentlog.Log3(ctx, m.log,
"not deleting compacted index blob used as inputs for compaction",
blobparam.BlobID("blobID", cl.metadata.BlobID),
logparam.Duration("age", age),
logparam.Duration("maxEventualConsistencySettleTime", maxEventualConsistencySettleTime))
continueView on GitHub (pinned to 82495e54b5)