crowdsecurity/crowdsec · error · BulkError
error creating transaction: %w: %w
Error message
error creating transaction: %w: %w
What it means
c.Ent.Tx(ctx) failed to open a database transaction before inserting the community-blocklist decisions; the error is wrapped with the BulkError sentinel for caller-side classification. This is an infrastructure failure, not a data problem: ent could not ask the driver to BEGIN a transaction.
Source
Thrown at pkg/database/alerts.go:258
SetLeakSpeed(*alertItem.Leakspeed).
SetSimulated(*alertItem.Simulated).
SetScenarioVersion(*alertItem.ScenarioVersion).
SetScenarioHash(*alertItem.ScenarioHash).
SetKind(alertItem.Kind).
SetRemediation(true) // it's from CAPI, we always have decisions
alertRef, err := alertB.Save(ctx)
if err != nil {
return 0, 0, 0, fmt.Errorf("error creating alert: %w: %w", err, BulkError)
}
if len(alertItem.Decisions) == 0 {
return alertRef.ID, 0, 0, nil
}
txClient, err := c.Ent.Tx(ctx)
if err != nil {
return 0, 0, 0, fmt.Errorf("error creating transaction: %w: %w", err, BulkError)
}
decOrigin := CapiMachineID
if *alertItem.Decisions[0].Origin == CapiMachineID || *alertItem.Decisions[0].Origin == CapiListsMachineID {
decOrigin = *alertItem.Decisions[0].Origin
} else {
log.Warningf("unexpected origin %s", *alertItem.Decisions[0].Origin)
}
deleted := 0
inserted := 0
decisionBuilders := make([]*ent.DecisionCreate, 0, len(alertItem.Decisions))
valueList := make([]string, 0, len(alertItem.Decisions))
for _, decisionItem := range alertItem.Decisions {
if decisionItem.Duration == nil {View on GitHub (pinned to 909b515798)
Solutions
- Verify database connectivity and re-run the pull; this is typically transient
- If SQLite, check for concurrent processes holding the DB (lsof on the .db file) or switch to a server DB
- Check driver connection-pool limits and the context deadline passed to SaveAlerts
- Inspect the wrapped driver error — 'driver: bad connection' means the pool needs recycling (restart the service)
Example fix
// before ctx := context.Background() // after (give the transaction a bounded deadline instead of inheriting a cancelled one) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel()
Defensive patterns
Strategy: retry
Validate before calling
// verify the DB is reachable before the bulk pull
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if _, err := c.Ent.Alert.Query().Limit(1).First(ctx); err != nil {
return fmt.Errorf("database unreachable: %w", err)
} Try / catch
if _, _, _, err := db.UpdateCommunityBlocklist(ctx, alert); err != nil {
if errors.Is(err, database.BulkError) {
// transient tx failure: back off and retry the pull
time.Sleep(time.Second)
return retryPull()
}
return err
} Prevention
- Set sane context deadlines for blocklist pulls so cancelled contexts don't leak into Tx()
- Use a server DB (PostgreSQL/MySQL) for concurrent access instead of SQLite
- Watch connection-pool saturation during large CAPI pulls
- Alert on DB connectivity so transaction failures are caught at the source
When it happens
Trigger: UpdateCommunityBlocklist (via SaveAlerts) with an alert that has decisions, when the underlying DB driver cannot start a transaction — connection already closed, SQLite lock contention, pool exhausted, or context cancelled/expired.
Common situations: Database server restarted or network blip during a large CAPI blocklist pull; SQLite database locked by a concurrent cscli command; connection-pool maxed out by other long-running queries.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- while getting allowlist %s: %s
- while replacing allowlist %s: %s
- while getting alert: %w
- while getting decision: %w
- %s: %w
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/c1c0e3d9fe9c0ecc.
Report an issue: GitHub.