crowdsecurity/crowdsec · error

unable to expire decisions for list %s : %w

Error message

unable to expire decisions for list %s : %w

What it means

After a valid blocklist_unsubscribe command, ManagementCmd expires all decisions with origin=lists and scenario=<blocklist name> via DBClient.ExpireDecisionsWithFilter. This error wraps any failure returned by that database operation, typically an SQLite/DB connectivity or lock problem, so the unsubscribe does not silently leave stale decisions.

Source

Thrown at pkg/apiserver/papi_cmd.go:219

		unsubscribeMsg := blocklistUnsubscribe{}

		if err := json.Unmarshal(data, &unsubscribeMsg); err != nil {
			return fmt.Errorf("message for '%s' contains bad data format: %w", message.Header.OperationType, err)
		}

		if unsubscribeMsg.Name == "" {
			return fmt.Errorf("message for '%s' contains bad data format: missing blocklist name", message.Header.OperationType)
		}

		p.Logger.Infof("Received blocklist_unsubscribe command from PAPI, unsubscribing from blocklist %s", unsubscribeMsg.Name)

		filter := make(map[string][]string)
		filter["origin"] = []string{types.ListOrigin}
		filter["scenario"] = []string{unsubscribeMsg.Name}

		_, deletedDecisions, err := p.DBClient.ExpireDecisionsWithFilter(ctx, filter)
		if err != nil {
			return fmt.Errorf("unable to expire decisions for list %s : %w", unsubscribeMsg.Name, err)
		}

		p.Logger.Infof("deleted %d decisions for list %s", len(deletedDecisions), unsubscribeMsg.Name)
	case "reauth":
		p.Logger.Infof("Received reauth command from PAPI, resetting token")
		p.apiClient.GetClient().Transport.(*apiclient.JWTTransport).ResetToken()
	case "force_pull":
		data, err := json.Marshal(message.Data)
		if err != nil {
			return err
		}

		forcePullMsg := forcePull{}

		if err := json.Unmarshal(data, &forcePullMsg); err != nil {
			return fmt.Errorf("message for '%s' contains bad data format: %w", message.Header.OperationType, err)
		}

View on GitHub (pinned to 909b515798)

Solutions

  1. Read the wrapped cause at the end of the error message to identify the DB failure (e.g. 'database is locked')
  2. Stop competing crowdsec processes (cscli, other agents sharing the same data_dir) and retry
  3. Check free disk space and permissions on the SQLite database file (default /var/lib/crowdsec/data/crowdsec.db)
  4. If corruption is suspected, restore from backup or run sqlite3 .recover on the DB
Defensive patterns

Strategy: retry

Validate before calling

// verify DB is writable/healthy before processing commands
if _, err := os.Stat(dbPath); err != nil {
    return fmt.Errorf("crowdsec database not found: %w", err)
}

Try / catch

if err := ManagementCmd(ctx, msg, p, false); err != nil {
    if strings.Contains(err.Error(), "unable to expire decisions") {
        log.Errorf("DB failure during unsubscribe, will retry: %v", err)
        return retryAfterBackoff(err)
    }
    return err
}

Prevention

When it happens

Trigger: ExpireDecisionsWithFilter returns an error while deleting decisions for origin=types.ListOrigin and scenario=unsubscribeMsg.Name — e.g. database locked, disk full, or corrupted DB file.

Common situations: SQLite database locked by a concurrent LAPI writer; disk quota exceeded on the machine hosting the crowdsec DB; database file corruption after an unclean shutdown.

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