crowdsecurity/crowdsec · error
unable to expire decisions for batch: %w
Error message
unable to expire decisions for batch: %w
What it means
applyAllowlistBatch expires existing decisions matching a batch of allowlist items via an ent decision Update with OR-ed conditions. If the batched UPDATE fails, this error wraps it and reports the count expired so far (totalCount) - earlier batches may already be committed, so the operation is partially applied.
Source
Thrown at pkg/database/allowlists.go:539
),
),
),
),
)
}
}
count, err := c.Ent.Decision.Update().
SetUntil(now).
Where(
decision.UntilGTE(now),
decision.IPSizeEQ(ipSize),
decision.Or(conditions...),
).
Save(ctx)
if err != nil {
return totalCount, fmt.Errorf("unable to expire decisions for batch: %w", err)
}
totalCount += count
c.Log.Debugf("expired %d decisions for batch of %d allowlist items", count, len(batch))
}
return totalCount, nil
}
View on GitHub (pinned to 909b515798)
Solutions
- Check totalCount in the wrapped result to see how far the expiry got before failing
- Resolve SQLite lock contention (stop concurrent writers, increase busy_timeout)
- Re-run ApplyAllowlistsToExistingDecisions; expiring is idempotent
- If batches are too large, reduce the batch size so each UPDATE touches fewer rows
Example fix
// before
if err != nil {
return totalCount, fmt.Errorf("unable to expire decisions for batch: %w", err)
}
// after - retry transient lock errors before giving up
if err := retryOnLock(ctx, 3, saveFn); err != nil {
return totalCount, fmt.Errorf("unable to expire decisions for batch: %w", err)
} Defensive patterns
Strategy: retry
Try / catch
count, err := applyBatch(ctx, items)
if err != nil {
// count = totalCount: batches already committed; expiring is idempotent, safe to retry
if isTransientDB(err) {
return retryWithBackoff(ctx, 3, func() error { _, err := applyBatch(ctx, items); return err })
}
return err
} Prevention
- Keep allowlist batches small so each UPDATE stays short-lived
- Use a busy_timeout so concurrent bouncer pulls don't abort the expiry update
- Re-run on failure: expiring decisions is idempotent
When it happens
Trigger: Calling applyAllowlistBatch via ApplyAllowlistsToExistingDecisions when a decision-update Save fails: SQLite lock contention, IO error, or an overly large OR-condition batch timing out.
Common situations: Large allowlists (many batches) against a busy SQLite DB during decision flushes; concurrent bouncer pulls locking decisions table.
Related errors
- while getting allowlist %s: %s
- while creating allowlist %s: %s
- unable to add values to allowlist: %w
- unable to check if value is allowlisted: %w
- unable to get allowlist items: %w
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/48b06dfcb9befaea.
Report an issue: GitHub.