crowdsecurity/crowdsec · error · DeleteFail

expire decisions with provided filter: %w: %w

Error message

expire decisions with provided filter: %w: %w

What it means

After the candidate decisions are fetched, ExpireDecisionsWithFilter calls ExpireDecisions to set their until_ts to now. If that bulk update fails, the underlying error is wrapped with the DeleteFail sentinel and this message, meaning the filter matched rows but the expiry write itself failed.

Source

Thrown at pkg/database/decisions.go:273

		default:
			return 0, nil, fmt.Errorf("'%s' doesn't exist: %w", param, InvalidFilter)
		}
	}

	decisions, err = decisionIPFilter(decisions, contains, rng)
	if err != nil {
		return 0, nil, err
	}

	decisionsToDelete, err := decisions.All(ctx)
	if err != nil {
		c.Log.Warningf("ExpireDecisionsWithFilter : %s", err)
		return 0, nil, fmt.Errorf("expire decisions with provided filter: %w", DeleteFail)
	}

	count, err := c.ExpireDecisions(ctx, decisionsToDelete)
	if err != nil {
		return 0, nil, fmt.Errorf("expire decisions with provided filter: %w: %w", err, DeleteFail)
	}

	return count, decisionsToDelete, err
}

func decisionIDs(decisions []*ent.Decision) []int {
	ids := make([]int, len(decisions))
	for i, d := range decisions {
		ids[i] = d.ID
	}

	return ids
}

// expireDecisionBatch expires the decisions as a single operation.
func (c *Client) expireDecisionBatch(ctx context.Context, batch []*ent.Decision, now time.Time) (int, error) {
	ids := decisionIDs(batch)

View on GitHub (pinned to 909b515798)

Solutions

  1. Retry the operation; the failure is at the write stage and the filter query already succeeded
  2. Check DB connectivity and lock contention (show processlist / SQLite busy_timeout)
  3. Verify disk space and DB write permissions for the crowdsec process
  4. Check errors.Is(err, database.DeleteFail) to map to a 500 in API handlers

Example fix

// before
count, _, err := client.ExpireDecisionsWithFilter(ctx, filter)
if err != nil { return err }
// after
count, _, err := client.ExpireDecisionsWithFilter(ctx, filter)
if errors.Is(err, database.DeleteFail) {
    return fmt.Errorf("decision expiry failed, check database: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// ensure DB is reachable and writable before the bulk update
if err := entClient.Ping(ctx); err != nil { return err }

Try / catch

if _, _, err := client.ExpireDecisionsWithFilter(ctx, filter); err != nil {
    if errors.Is(err, database.DeleteFail) {
        // retry once after short backoff; check inner error for lock/conn issues
    }
    return err
}

Prevention

When it happens

Trigger: ExpireDecisions returning an error: DB connection dropped between query and update, write lock contention on decision table, context cancelled mid-update.

Common situations: Concurrent LAPI writers contending on the decisions table; SQLite 'database is locked' under parallel cscli commands; DB going away during long maintenance jobs.

Related errors


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