{"record":{"id":"f4b51ba28576f714","repo":"vxcontrol/pentagi","slug":"failed-to-update-flow-provider-in-db-w","errorCode":null,"errorMessage":"failed to update flow provider in DB: %w","messagePattern":"failed to update flow provider in DB: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/flow.go","lineNumber":927,"sourceCode":"\t\tlogger.Debug(\"provider is the same, skipping switch\")\n\t\treturn nil\n\t}\n\n\tlogger.Info(\"switching flow provider\")\n\n\t// Every persisted value is taken from prv (and the template SetProvider\n\t// resolved for it) rather than re-read from the shared flow provider, so a\n\t// concurrent switch cannot interleave into a mixed-provider row.\n\tflow, err := fw.flowCtx.DB.UpdateFlowProvider(ctx, database.UpdateFlowProviderParams{\n\t\tModelProviderName:  prv.Name().String(),\n\t\tModelProviderType:  database.ProviderType(prv.Type()),\n\t\tToolCallIDTemplate: tcIDTemplate,\n\t\tModel:              prv.Model(pconfig.OptionsTypePrimaryAgent),\n\t\tID:                 fw.flowCtx.FlowID,\n\t})\n\tif err != nil {\n\t\tlogger.WithError(err).Error(\"failed to update flow provider in DB\")\n\t\treturn fmt.Errorf(\"failed to update flow provider in DB: %w\", err)\n\t}\n\n\tlogger.WithFields(logrus.Fields{\n\t\t\"new_tool_call_id_template\": tcIDTemplate,\n\t\t\"new_model\":                 prv.Model(pconfig.OptionsTypePrimaryAgent),\n\t}).Info(\"provider switched successfully\")\n\n\tif containers, err := fw.flowCtx.DB.GetFlowContainers(ctx, fw.flowCtx.FlowID); err == nil {\n\t\tfw.flowCtx.Publisher.FlowUpdated(ctx, flow, containers)\n\t}\n\n\treturn nil\n}\n\nfunc (fw *flowWorker) finish() error {\n\tif err := fw.ctx.Err(); err != nil {\n\t\tif errors.Is(err, context.Canceled) {\n\t\t\treturn nil","sourceCodeStart":909,"sourceCodeEnd":945,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/flow.go#L909-L945","documentation":"After a successful in-memory SetProvider, switchProvider persists the new provider name/type, resolved tool_call_id_template and primary-agent model to the flows table via UpdateFlowProvider. This error means the DB write failed, so the flow runs on the new provider in memory but a restart would revert it — memory and database are inconsistent until fixed.","triggerScenarios":"UpdateFlowProvider returns an error when switching provider on a live flow — DB connection lost, context cancelled during the switch, the flow row was deleted concurrently, or a constraint/trigger on flows fails.","commonSituations":"PostgreSQL restart/pool exhaustion mid-switch; flow deleted by another operator while the switch was in flight; request context timed out; migration mismatch leaving flows table in an unexpected state.","solutions":["Check the wrapped error and backend DB logs for the actual SQL failure","Verify PostgreSQL connectivity and that the flows row still exists for the flow ID","Retry the provider switch once connectivity is restored so the DB row is updated","Reconcile by restarting the flow, which rebuilds the in-memory provider from the DB row"],"exampleFix":"// before: fire-and-forget switch can leave stale DB row\nif err := fw.switchProvider(ctx, prv); err != nil {\n    logger.Warn(\"switch failed, ignoring\")\n}\n// after: surface and let caller retry / reconcile\nif err := fw.switchProvider(ctx, prv); err != nil {\n    return fmt.Errorf(\"provider switch incomplete for flow %d: %w\", fw.flowCtx.FlowID, err)\n}","handlingStrategy":"retry","validationCode":"var exists int\nif err := db.QueryRowContext(ctx, `SELECT COUNT(1) FROM flows WHERE id=$1`, flowID).Scan(&exists); err != nil || exists == 0 {\n    return fmt.Errorf(\"flow %d not found; cannot update provider\", flowID)\n}","typeGuard":"func flowExists(ctx context.Context, db DB, id int64) bool {\n    var n int\n    _ = db.QueryRowContext(ctx, `SELECT COUNT(1) FROM flows WHERE id=$1`, id).Scan(&n)\n    return n == 1\n}","tryCatchPattern":"_, err := fw.flowCtx.DB.UpdateFlowProvider(ctx, params)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || isTransientDBError(err) {\n        // retry with backoff\n    }\n    return fmt.Errorf(\"failed to update flow provider in DB: %w\", err)\n}","preventionTips":["Keep PostgreSQL connectivity checked before performing runtime switches","Retry transient DB errors with backoff; DB row and memory otherwise diverge","Avoid deleting flows while a provider switch is in flight","Monitor DB connection pool saturation"],"tags":["go","database","postgres","provider-switch"],"backgroundTag":"database-write-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}