crowdsecurity/crowdsec · error

nil alert builder at index

Error message

nil alert builder at index %d

What it means

saveAlerts iterates over the per-alert creation plans built by createAlertBatch and extracts each plan's ent.AlertCreate builder for the CreateBulk call. This error is an internal invariant check: a plan entry exists but its builder is nil, which should never happen since the builder is always constructed via txEnt.Alert.Create(). It guards against a programming error in batch construction, not a user or environment problem.

Solutions

  1. Inspect the code path that constructs alertCreatePlan values and ensure txEnt.Alert.Create() is called for every appended plan before saveAlerts
  2. Check for recent refactors/patches to createAlertBatch or saveAlerts that may append empty plans; fix them to skip the append instead
  3. If hit in production, treat it as a bug: report it with the alert UUIDs being processed; the whole batch is rolled back so no data is lost

Example fix

// before
plan := alertCreatePlan{decisions: decisions}
batch = append(batch, plan)

// after
builder := txEnt.Alert.Create().SetScenario(*alertItem.Scenario).SetMessage(*alertItem.Message)
batch = append(batch, alertCreatePlan{builder: builder, decisions: decisions})
Defensive patterns

Strategy: type-guard

Validate before calling

for i, plan := range plans {
	if plan.builder == nil {
		return fmt.Errorf("plan %d has nil builder; skipping batch", i)
	}
}

Type guard

func validPlan(p alertCreatePlan) bool { return p.builder != nil }

Try / catch

ids, err := client.CreateAlert(ctx, machineID, alerts)
if err != nil && strings.Contains(err.Error(), "nil alert builder") {
	// internal bug: log and report; no user-side fix
	log.Errorf("crowdsec internal error: %v", err)
}

Prevention

When it happens

Trigger: An alertCreatePlan entry was appended to the batch with a nil builder. With the current code in createAlertBatch this is unreachable; it could only occur if the batch-construction logic changes to conditionally skip builder creation while still appending a plan, or if an AlertCreate pointer is nil-ed out elsewhere before saveAlerts runs.

Common situations: Practically only seen after a code refactor or a plugin/patch that builds alertCreatePlan values manually and forgets to assign the builder, or when passing a zero-value alertCreatePlan struct (e.g. via a slice of partially initialized plans) into saveAlerts.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at pkg/database/alerts.go:595

		}

		return err
	}

	return fmt.Errorf("exceeded %d busy retries", maxLockRetries)
}

func (c *Client) saveAlerts(ctx context.Context, client *ent.Client, batch []alertCreatePlan) ([]string, error) {
	if len(batch) == 0 {
		log.Warningf("no alerts to create, discarded?")
		return nil, nil
	}

	// extract builders in the same order
	builders := make([]*ent.AlertCreate, len(batch))
	for i := range batch {
		if batch[i].builder == nil {
			return nil, fmt.Errorf("nil alert builder at index %d", i)
		}

		builders[i] = batch[i].builder
	}

	alertsCreateBulk, err := client.Alert.CreateBulk(builders...).Save(ctx)
	if err != nil {
		return nil, fmt.Errorf("bulk creating alert: %w: %w", err, BulkError)
	}

	ret := make([]string, len(alertsCreateBulk))
	for i, a := range alertsCreateBulk {
		ret[i] = strconv.Itoa(a.ID)

		d := batch[i].decisions
		if len(d) == 0 {
			continue
		}

View on GitHub (pinned to 909b515798)