{"record":{"id":"fc4f835e6164afc0","repo":"crowdsecurity/crowdsec","slug":"creating-alert-transaction-w-w","errorCode":null,"errorMessage":"creating alert transaction: %w: %w","messagePattern":"creating alert transaction: %w: %w","errorType":"exception","errorClass":"BulkError","httpStatus":null,"severity":"error","filePath":"pkg/database/alerts.go","lineNumber":636,"sourceCode":"\t\t\t\treturn err\n\t\t\t})\n\t\t}); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"attach decisions to alert %d: %w\", a.ID, err)\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n\ntype alertCreatePlan struct {\n\tbuilder   *ent.AlertCreate\n\tdecisions []*ent.Decision\n}\n\nfunc (c *Client) createAlertBatch(ctx context.Context, machineID string, owner *ent.Machine, alerts []*models.Alert) ([]string, error) {\n\ttx, err := c.Ent.Tx(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"creating alert transaction: %w: %w\", err, BulkError)\n\t}\n\n\ttxEnt := tx.Client()\n\n\tbatch := make([]alertCreatePlan, 0, len(alerts))\n\n\tfor _, alertItem := range alerts {\n\t\tstartAtTime, stopAtTime := parseAlertTimes(alertItem, c.Log)\n\n\t\t// display proper alert in logs\n\t\tfor _, disp := range alertItem.FormatAsStrings(machineID, log.StandardLogger()) {\n\t\t\tc.Log.Info(disp)\n\t\t}\n\n\t\tevents, err := buildEventCreates(ctx, c.Log, txEnt, machineID, alertItem)\n\t\tif err != nil {\n\t\t\treturn nil, rollbackOnError(tx, err, fmt.Sprintf(\"building events for alert %s\", alertItem.UUID))\n\t\t}","sourceCodeStart":618,"sourceCodeEnd":654,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/database/alerts.go#L618-L654","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the wrapped driver error: if the connection is down, verify DB host/credentials in crowdsec.yaml and that the DB server is running","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","If the context was cancelled, increase the client/HTTP timeout that cancelled the request and retry","On MySQL/Postgres 'too many connections', raise max_connections or reduce crowdsec concurrency/pool size","Verify disk space and file permissions on the SQLite database path"],"exampleFix":"// before\nids, err := client.CreateAlert(ctx, machineID, alerts)\nif err != nil {\n\treturn err\n}\n\n// after\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\nids, err := client.CreateAlert(ctx, machineID, alerts)\nif err != nil {\n\tif isConnError(err) {\n\t\t// reconnect / check DB availability before retrying\n\t}\n\treturn err\n}","handlingStrategy":"try-catch","validationCode":"if err := ctx.Err(); err != nil {\n\treturn fmt.Errorf(\"context already cancelled: %w\", err) // avoid doomed call\n}","typeGuard":"null","tryCatchPattern":"ids, err := client.CreateAlert(ctx, machineID, alerts)\nif err != nil && strings.Contains(err.Error(), \"creating alert transaction\") {\n\t// DB unreachable/locked: check connectivity, then retry with fresh context\n\treturn retryWithBackoff(func() error {\n\t\tids, err = client.CreateAlert(ctx, machineID, alerts)\n\t\treturn err\n\t})\n}","preventionTips":["Give LAPI/alert-creation calls a generous but finite context timeout","Verify DB connectivity settings in crowdsec.yaml before bulk operations","Keep the SQLite DB on local storage with correct file permissions","On MySQL/Postgres, size the connection pool below the server's max_connections","Retry with backoff: transaction start is fully idempotent"],"tags":["database","transaction","connection","crowdsec"],"backgroundTag":"database-query-failed","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}