{"record":{"id":"1ce4952ad75bbd74","repo":"crowdsecurity/crowdsec","slug":"creating-alert-decisions-w","errorCode":null,"errorMessage":"creating alert decisions: %w","messagePattern":"creating alert decisions: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/database/alerts.go","lineNumber":178,"sourceCode":"\t\t\tSetEndSuffix(rng.End.Sfx).\n\t\t\tSetIPSize(int64(rng.Size())).\n\t\t\tSetValue(*decisionItem.Value).\n\t\t\tSetScope(*decisionItem.Scope).\n\t\t\tSetOrigin(*decisionItem.Origin).\n\t\t\tSetSimulated(*alertItem.Simulated).\n\t\t\tSetUUID(decisionItem.UUID).\n\t\t\tSetOwnerID(foundAlert.ID)\n\n\t\tdecisionBuilders = append(decisionBuilders, decisionBuilder)\n\t}\n\n\t// create missing decisions in batches\n\n\tdecisions := make([]*ent.Decision, 0, len(decisionBuilders))\n\tif err := slicetools.Batch(ctx, decisionBuilders, c.decisionBulkSize, func(ctx context.Context, b []*ent.DecisionCreate) error {\n\t\tret, err := c.Ent.Decision.CreateBulk(b...).Save(ctx)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"creating alert decisions: %w\", err)\n\t\t}\n\t\tdecisions = append(decisions, ret...)\n\t\treturn nil\n\t}); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn \"\", nil\n}\n\n// UpdateCommunityBlocklist is called to update either the community blocklist (or other lists the user subscribed to)\n// it takes care of creating the new alert with the associated decisions, and it will as well deleted the \"older\" overlapping decisions:\n// 1st pull, you get decisions [1,2,3]. it inserts [1,2,3]\n// 2nd pull, you get decisions [1,2,3,4]. it inserts [1,2,3,4] and will try to delete [1,2,3,4] with a different alert ID and same origin\nfunc (c *Client) UpdateCommunityBlocklist(ctx context.Context, alertItem *models.Alert) (int, int, int, error) {\n\tif alertItem == nil {\n\t\treturn 0, 0, 0, errors.New(\"nil alert\")\n\t}","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/database/alerts.go#L160-L196","documentation":"This error wraps any failure from the ent ORM bulk-insert of a batch of decision rows (c.Ent.Decision.CreateBulk(...).Save(ctx)) while creating the decisions attached to an alert. It is raised inside the slicetools.Batch callback, so the offending batch is retried/aborted per c.decisionBulkSize. The wrapped ent error usually carries the underlying database driver message (constraint violation, connection drop, context cancellation).","triggerScenarios":"Inserting alerts with many decisions when one decision row violates a DB constraint or the database is unreachable, the context is cancelled mid-batch, or a duplicate UUID/unique index conflicts on the decisions table.","commonSituations":"SQLite database file locked by another crowdsec process or corrupted; PostgreSQL/MySQL connection dropped during a large CAPI pull; batch size larger than the driver's parameter limit; duplicate decision UUIDs pushed by a misbehaving list source.","solutions":["Inspect the wrapped error for the driver-level cause (duplicate key, locked db, connection refused) and fix that first","Check database connectivity and that no other crowdsec/cscli process holds a lock on the SQLite file (or run vacuum)","Reduce c.decisionBulkSize if the driver reports 'too many parameters'","Retry the operation; transient DB errors during bulk insert are often resolved on a subsequent pull"],"exampleFix":"// before\nc.decisionBulkSize = 1000\n// after (halve the batch to stay under driver parameter limits)\nc.decisionBulkSize = 500","handlingStrategy":"try-catch","validationCode":"// Go: sanity-check decisions and DB before the call\nfor _, d := range alert.Decisions {\n    if d.UUID == \"\" || d.Duration == nil {\n        return fmt.Errorf(\"decision missing uuid/duration\")\n    }\n}\nif err := dbPing(ctx); err != nil { return err }","typeGuard":"func validDecision(d *models.Decision) bool {\n    return d != nil && d.UUID != \"\" && d.Duration != nil && d.Origin != nil\n}","tryCatchPattern":"id, err := client.SaveAlerts(ctx, alert)\nif err != nil {\n    var bulk *ent.ValidationError\n    if errors.Is(err, ErrBulk) || ent.IsConstraintError(err) {\n        // dedupe/skip conflicting decision, retry\n    }\n    return fmt.Errorf(\"save alerts: %w\", err)\n}","preventionTips":["Keep only one crowdsec/cscli writer against the SQLite file","Keep decisionBulkSize within driver parameter limits","Monitor DB connectivity before large CAPI pulls","Rely on the retry semantics of the CAPI pull to heal transient failures"],"tags":["database","ent","bulk-insert"],"backgroundTag":"database-write-failed","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}