{"record":{"id":"b1be815ff1de5557","repo":"crowdsecurity/crowdsec","slug":"unable-to-update-base-bouncer-metrics-in-database","errorCode":null,"errorMessage":"unable to update base bouncer metrics in database: %w","messagePattern":"unable to update base bouncer metrics in database: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/database/bouncers.go","lineNumber":37,"sourceCode":"\treturn fmt.Sprintf(\"'%s' does not exist\", e.BouncerName)\n}\n\nfunc (c *Client) BouncerUpdateBaseMetrics(ctx context.Context, bouncerName string, bouncerType string, baseMetrics models.BaseMetrics) error {\n\tos := baseMetrics.Os\n\tfeatures := strings.Join(baseMetrics.FeatureFlags, \",\")\n\n\t_, err := c.Ent.Bouncer.\n\t\tUpdate().\n\t\tWhere(bouncer.NameEQ(bouncerName)).\n\t\tSetNillableVersion(baseMetrics.Version).\n\t\tSetOsname(*os.Name).\n\t\tSetOsfamily(os.Family).\n\t\tSetOsversion(*os.Version).\n\t\tSetFeatureflags(features).\n\t\tSetType(bouncerType).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to update base bouncer metrics in database: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *Client) SelectBouncers(ctx context.Context, apiKeyHash string, authType string) ([]*ent.Bouncer, error) {\n\t// Order by ID so manually created bouncer will be first in the list to use as the base name\n\t// when automatically creating a new entry if API keys are shared\n\tresult, err := c.Ent.Bouncer.Query().Where(bouncer.APIKeyEQ(apiKeyHash), bouncer.AuthTypeEQ(authType)).Order(ent.Asc(bouncer.FieldID)).All(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result, nil\n}\n\nfunc (c *Client) SelectBouncerWithIP(ctx context.Context, apiKeyHash string, clientIP string) (*ent.Bouncer, error) {\n\tresult, err := c.Ent.Bouncer.Query().Where(bouncer.APIKeyEQ(apiKeyHash), bouncer.IPAddressEQ(clientIP)).First(ctx)","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/database/bouncers.go#L19-L55","documentation":"BouncerUpdateBaseMetrics persists bouncer telemetry (OS family/version, feature flags, bouncer type) via an ent Bouncer update. If the Save fails, this error wraps the DB failure. The bouncer's auth still works; only metrics recording failed.","triggerScenarios":"Calling BouncerUpdateBaseMetrics (from updateBaseMetrics, on bouncer auth) when the ent update fails: the bouncer row was deleted concurrently, DB locked, or IO error.","commonSituations":"A bouncer authenticating at the same moment an admin deletes it via 'cscli bouncers delete'; heavy SQLite write load from the daemon.","solutions":["Verify the bouncer still exists ('cscli bouncers list'); re-auth re-creates missing rows","Check the wrapped error for 'database is locked' and reduce concurrent writers","Have the bouncer re-authenticate - metrics update is retried on each auth"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"if _, err := c.SelectBouncers(ctx, apiKeyHash, authType); err != nil {\n    // bouncer row gone; auth flow will re-create it before metrics update\n}","typeGuard":null,"tryCatchPattern":"if err := c.BouncerUpdateBaseMetrics(ctx, bouncerID, os, features, bouncerType); err != nil {\n    c.Log.Warnf(\"bouncer metrics update failed (auth unaffected): %v\", err)\n    return nil // do not fail authentication for a metrics bookkeeping error\n}","preventionTips":["Don't delete a bouncer while it is actively authenticating","Log metrics-update failures as warnings; they don't affect auth","Metrics are re-sent on every bouncer auth, so transient failures self-heal"],"tags":["database","bouncer","metrics","ent"],"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-14T05:17:10.506Z"}