{"record":{"id":"a7c10e2fe2affb03","repo":"vxcontrol/pentagi","slug":"failed-to-bulk-update-flows-provider-name-w","errorCode":null,"errorMessage":"failed to bulk-update flows provider name: %w","messagePattern":"failed to bulk-update flows provider name: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/flows.go","lineNumber":491,"sourceCode":"\tif _, err := fc.provs.GetProvider(ctx, oldName, userID); err == nil {\n\t\tlogger.Debug(\"old provider name still resolves, nothing to reassign\")\n\t\treturn nil\n\t}\n\n\t// Detached from the caller's request context: these are two short statements\n\t// and the reference must not be left half-rewritten because a browser tab\n\t// was closed. The timeout keeps a stuck DB from pinning the goroutine.\n\tctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), reassignProviderTimeout)\n\tdefer cancel()\n\n\tflows, flowsErr := fc.db.UpdateFlowsProviderNameByOldName(ctx, database.UpdateFlowsProviderNameByOldNameParams{\n\t\tNewName: newName.String(),\n\t\tUserID:  userID,\n\t\tOldName: oldName.String(),\n\t})\n\tif flowsErr != nil {\n\t\tlogger.WithError(flowsErr).Error(\"failed to bulk-update flows provider name\")\n\t\tflowsErr = fmt.Errorf(\"failed to bulk-update flows provider name: %w\", flowsErr)\n\t}\n\n\tassistants, asstErr := fc.db.UpdateAssistantsProviderNameByOldName(\n\t\tctx, database.UpdateAssistantsProviderNameByOldNameParams{\n\t\t\tNewName: newName.String(),\n\t\t\tUserID:  userID,\n\t\t\tOldName: oldName.String(),\n\t\t})\n\tif asstErr != nil {\n\t\tlogger.WithError(asstErr).Error(\"failed to bulk-update assistants provider name\")\n\t\tasstErr = fmt.Errorf(\"failed to bulk-update assistants provider name: %w\", asstErr)\n\t}\n\n\t// Publishing happens only after both writes are done. A subscriber that is\n\t// not draining its channel makes each publish cost up to the subscription\n\t// send timeout, so doing it in between would let a wedged websocket client\n\t// eat the deadline and starve the second UPDATE.\n\tfor _, flow := range flows {","sourceCodeStart":473,"sourceCodeEnd":509,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/flows.go#L473-L509","documentation":"Raised inside reassignFlowsProvider (used by RenameFlowsProvider and ResetFlowsProviderToDefault) when the bulk UPDATE of the flows table — UpdateFlowsProviderNameByOldName — fails while repointing all of a user's flows from oldName to newName after a custom provider rename. The SQL error is logged and wrapped as \"failed to bulk-update flows provider name: %w\". The function then still attempts the assistants update and joins errors, so a flows-only failure does not skip the second table.","triggerScenarios":"Calling RenameFlowsProvider or ResetFlowsProviderToDefault for a user whose old provider name no longer resolves, when the UPDATE flows SET ... WHERE user_id=? AND provider_name=oldName query fails: DB unreachable, lock contention/timeout, or the detached 30s reassignProviderTimeout context expires.","commonSituations":"PostgreSQL overloaded or under lock contention during a provider rename from the settings UI; slow/failing DB causing context deadline; network blip between backend and database mid-rename; stale connection pool after a DB failover.","solutions":["Check server logs — the underlying SQL error is logged with logger.WithError before wrapping.","Verify PostgreSQL health and retry the rename; the sweeps only match rows still bearing oldName, making the operation idempotent.","Check for lock contention on the flows table (e.g. other long-running transactions).","Confirm the update completes within reassignProviderTimeout; tune the timeout if the DB is slow."],"exampleFix":"null","handlingStrategy":"retry","validationCode":"// verify provider names differ before triggering a cascade\nif oldName == newName {\n    return nil // reassignFlowsProvider no-ops here anyway\n}","typeGuard":"null","tryCatchPattern":"if err := flows.RenameFlowsProvider(ctx, userID, old, new); err != nil {\n    if strings.Contains(err.Error(), \"failed to bulk-update flows provider name\") {\n        // idempotent: only rows still bearing oldName are rewritten\n        time.Sleep(backoff)\n        err = flows.RenameFlowsProvider(ctx, userID, old, new)\n    }\n    return err\n}","preventionTips":["Rely on idempotency: the update only matches rows still bearing oldName, so safe retries are free.","Watch DB lock contention on flows during provider renames.","Keep reassignProviderTimeout generous enough for a loaded database.","Alert on the logger error line so partial cascades (flows failed, assistants pending) are visible."],"tags":["database","go","provider","bulk-update"],"backgroundTag":"bulk-update-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}