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

  1. Check totalCount in the wrapped result to see how far the expiry got before failing
  2. Resolve SQLite lock contention (stop concurrent writers, increase busy_timeout)
  3. Re-run ApplyAllowlistsToExistingDecisions; expiring is idempotent
  4. 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

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


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/48b06dfcb9befaea. Report an issue: GitHub.