crowdsecurity/crowdsec · error · BulkError

creating alert transaction: %w: %w

Error message

creating alert transaction: %w: %w

What it means

createAlertBatch opens a database transaction (c.Ent.Tx(ctx)) before building and saving all alerts in the batch. This error wraps a failure to start that transaction, tagged with the BulkError sentinel. It is an infrastructure-level failure — the driver refused BEGIN — so no alert work was attempted.

Source

Thrown at pkg/database/alerts.go:636

				return err
			})
		}); err != nil {
			return nil, fmt.Errorf("attach decisions to alert %d: %w", a.ID, err)
		}
	}

	return ret, nil
}

type alertCreatePlan struct {
	builder   *ent.AlertCreate
	decisions []*ent.Decision
}

func (c *Client) createAlertBatch(ctx context.Context, machineID string, owner *ent.Machine, alerts []*models.Alert) ([]string, error) {
	tx, err := c.Ent.Tx(ctx)
	if err != nil {
		return nil, fmt.Errorf("creating alert transaction: %w: %w", err, BulkError)
	}

	txEnt := tx.Client()

	batch := make([]alertCreatePlan, 0, len(alerts))

	for _, alertItem := range alerts {
		startAtTime, stopAtTime := parseAlertTimes(alertItem, c.Log)

		// display proper alert in logs
		for _, disp := range alertItem.FormatAsStrings(machineID, log.StandardLogger()) {
			c.Log.Info(disp)
		}

		events, err := buildEventCreates(ctx, c.Log, txEnt, machineID, alertItem)
		if err != nil {
			return nil, rollbackOnError(tx, err, fmt.Sprintf("building events for alert %s", alertItem.UUID))
		}

View on GitHub (pinned to 909b515798)

Solutions

  1. Check the wrapped driver error: if the connection is down, verify DB host/credentials in crowdsec.yaml and that the DB server is running
  2. If SQLite is locked, ensure the file is on a local filesystem and no other process holds a write lock; restart crowdsec if the pool is wedged
  3. If the context was cancelled, increase the client/HTTP timeout that cancelled the request and retry
  4. On MySQL/Postgres 'too many connections', raise max_connections or reduce crowdsec concurrency/pool size
  5. Verify disk space and file permissions on the SQLite database path

Example fix

// before
ids, err := client.CreateAlert(ctx, machineID, alerts)
if err != nil {
	return err
}

// after
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
ids, err := client.CreateAlert(ctx, machineID, alerts)
if err != nil {
	if isConnError(err) {
		// reconnect / check DB availability before retrying
	}
	return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if err := ctx.Err(); err != nil {
	return fmt.Errorf("context already cancelled: %w", err) // avoid doomed call
}

Type guard

null

Try / catch

ids, err := client.CreateAlert(ctx, machineID, alerts)
if err != nil && strings.Contains(err.Error(), "creating alert transaction") {
	// DB unreachable/locked: check connectivity, then retry with fresh context
	return retryWithBackoff(func() error {
		ids, err = client.CreateAlert(ctx, machineID, alerts)
		return err
	})
}

Prevention

When it happens

Trigger: c.Ent.Tx(ctx) fails because the database connection is down or pooled out, the driver cannot begin a transaction (SQLite file locked/unreadable, MySQL 'max_connections' reached, server gone away), or the context is already cancelled/expired.

Common situations: SQLite DB file on NFS or with wrong permissions preventing locking; MySQL/Postgres connection pool exhausted under load; database restarted mid-run; request context cancelled by an LAPI client timeout before the transaction began.

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/fc4f835e6164afc0. Report an issue: GitHub.