crowdsecurity/crowdsec · error

could not get last alert: %w

Error message

could not get last alert: %w

What it means

FlushAlerts wraps an error returned while querying the most recent alert from the alerts table. The library throws it when the ent query used to determine the flush boundary (max-age or max-items) fails, aborting the whole flush operation.

Source

Thrown at pkg/database/flush.go:367

	}

	if maxItems > 0 {
		// We get the highest id for the alerts
		// We subtract MaxItems to avoid deleting alerts that are not old enough
		// This gives us the oldest alert that we want to keep
		// We then delete all the alerts with an id lower than this one
		// We can do this because the id is auto-increment, and the database won't reuse the same id twice
		lastAlert, err := c.QueryAlertWithFilter(ctx, map[string][]string{
			"sort":  {"DESC"},
			"limit": {"1"},
			// we do not care about fetching the edges, we just want the id
			"with_decisions": {"false"},
		})
		c.Log.Debugf("FlushAlerts (last alert): %+v", lastAlert)

		if err != nil {
			c.Log.Errorf("FlushAlerts: could not get last alert: %s", err)
			return fmt.Errorf("could not get last alert: %w", err)
		}

		if len(lastAlert) != 0 {
			maxid := lastAlert[0].ID - maxItems

			c.Log.Debugf("FlushAlerts (max id): %d", maxid)

			if maxid > 0 {
				// This may lead to orphan alerts (at least on MySQL), but the next time the flush job will run, they will be deleted
				// Alerts that still carry an active decision are kept regardless of the count: deleting them would
				// cascade-delete the live decision. They are flushed on a later run, once their decisions expire.
				deletedByNbItem, err = c.Ent.Alert.Delete().Where(
					alert.IDLT(maxid),
					alertWithoutActiveDecision(time.Now().UTC()),
				).Exec(ctx)
				if err != nil {
					c.Log.Errorf("FlushAlerts: Could not delete alerts: %s", err)
					return fmt.Errorf("could not delete alerts: %w", err)

View on GitHub (pinned to 909b515798)

Solutions

  1. Check database connectivity and that the DB file/server is reachable and writable
  2. Check crowdsec logs immediately above this error for the underlying ent/SQL driver message
  3. If SQLite, ensure no other process holds the DB and the file is not corrupted (run `sqlite3 crowdsec.db 'PRAGMA integrity_check'`)
  4. Retry after transient outage; flush will resume on next cycle

Example fix

// before (host down)
if err != nil { return fmt.Errorf("could not get last alert: %w", err) }
// after: verify DB reachability before flush
if err := c.Ent.Alert.Query().Limit(1).StringP(alert.FieldID); err != nil { log.Warn("db unreachable, skipping flush"); return nil }
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure DB reachable before flush
if err := c.Ent.Alert.Query().Limit(1).Select(alert.FieldID).String(ctx); err != nil {
    // skip this flush cycle
}

Try / catch

err := c.FlushAlerts(ctx, since, maxItems)
if err != nil && strings.Contains(err.Error(), "could not get last alert") {
    log.Warnf("flush skipped, db read failed: %v", err)
}

Prevention

When it happens

Trigger: The ent Alert query used to fetch the last alert returns an error — typically a database connectivity loss, closed/locked SQLite file, or context cancellation while FlushAlerts runs its maxItems/maxAge branch.

Common situations: SQLite database file locked by another crowdsec process or deleted mid-run; MySQL/Postgres down or credentials rotated; ctx canceled because the machine is shutting down during flush.

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