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

  1. Verify database connectivity and re-run the pull; this is typically transient
  2. If SQLite, check for concurrent processes holding the DB (lsof on the .db file) or switch to a server DB
  3. Check driver connection-pool limits and the context deadline passed to SaveAlerts
  4. 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

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


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