crowdsecurity/crowdsec · error · BulkError
committing alert transaction: %w: %w
Error message
committing alert transaction: %w: %w
What it means
At the end of createAlertBatch, the transaction containing all alert inserts, events, metas, and decision links is committed. This error wraps a commit failure with the BulkError sentinel. All work succeeded at the statement level but the database refused to make it durable (e.g. commit-time lock conflict, connection loss, deferred constraint failure, disk I/O error).
Source
Thrown at pkg/database/alerts.go:718
if owner != nil {
builder.SetOwner(owner)
}
batch = append(batch, alertCreatePlan{
builder: builder,
decisions: decisions,
})
}
// Save alerts, then attach decisions with retry logic
ids, err := c.saveAlerts(ctx, txEnt, batch)
if err != nil {
return nil, rollbackOnError(tx, err, "saving alerts")
}
if err := tx.Commit(); err != nil {
return nil, fmt.Errorf("committing alert transaction: %w: %w", err, BulkError)
}
return ids, nil
}
func (c *Client) CreateAlert(ctx context.Context, machineID string, alertList []*models.Alert) ([]string, error) {
var (
owner *ent.Machine
err error
)
if machineID != "" {
owner, err = c.QueryMachineByID(ctx, machineID)
if err != nil {
if !errors.Is(err, UserNotExists) {
return nil, fmt.Errorf("machine '%s': %w", machineID, err)
}
View on GitHub (pinned to 909b515798)
Solutions
- Read the wrapped driver error to distinguish lock, connection, or I/O failure
- Simply retry CreateAlert — the commit rolled back, so the batch was never persisted and resubmission is safe
- For SQLite lock errors, enable WAL journal mode and keep the DB on local disk; or migrate to MySQL/Postgres for high-concurrency setups
- For connection errors, check DB server logs and network stability; adjust idle timeouts (e.g. MySQL wait_timeout) for long-running crowdsec processes
- Free disk space / check I/O health on the database volume
Example fix
// before
ids, err := client.CreateAlert(ctx, machineID, alerts)
if err != nil {
panic(err)
}
// after
ids, err := client.CreateAlert(ctx, machineID, alerts)
if err != nil {
if isRetryableDBError(err) { // lock/connection failures: tx rolled back, safe to retry
ids, err = client.CreateAlert(ctx, machineID, alerts)
}
if err != nil {
return fmt.Errorf("alert creation failed: %w", err)
}
} Defensive patterns
Strategy: retry
Validate before calling
if err := ctx.Err(); err != nil {
return fmt.Errorf("context cancelled before commit: %w", err)
} Type guard
null
Try / catch
ids, err := client.CreateAlert(ctx, machineID, alerts)
if err != nil && strings.Contains(err.Error(), "committing alert transaction") {
// commit failed but tx rolled back; safe to retry after backoff
time.Sleep(backoff)
ids, err = client.CreateAlert(ctx, machineID, alerts)
} Prevention
- Treat commit failure as safe-to-retry: nothing was persisted
- Enable WAL mode for SQLite to reduce commit-time lock contention
- Keep the database volume healthy: monitor disk space and I/O errors
- On remote DBs, tune idle timeouts so long batches don't lose the connection at commit
- Reduce write concurrency on SQLite or migrate to MySQL/Postgres for heavy setups
When it happens
Trigger: tx.Commit() returns an error: SQLite 'database is locked' at commit time under concurrent writers, connection dropped between the last statement and COMMIT on MySQL/Postgres, disk-full or I/O error while flushing, or a constraint deferred to commit time.
Common situations: SQLite contention when bouncers and cscli write simultaneously and the commit races with another writer's lock; MySQL 'server has gone away' due to wait_timeout on a long batch; disk full on the DB volume precisely at commit; network partition to a remote database during large batches.
Related errors
- unable to expire decisions for list %s : %w
- %s: %w
- bulk creating alert: %w: %w
- unable to add values to allowlist: %w
- creating machine '%s': %w
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/82ed8142ef73ee52.
Report an issue: GitHub.