{"record":{"id":"728283f16b96ee05","repo":"vxcontrol/pentagi","slug":"failed-to-set-provider-w","errorCode":null,"errorMessage":"failed to set provider: %w","messagePattern":"failed to set provider: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/flow.go","lineNumber":905,"sourceCode":"// Fixing that properly means keeping a per-model template registry, which is out\n// of scope here; if real users hit it, this is the place to start.\nfunc (fw *flowWorker) switchProvider(ctx context.Context, prv provider.Provider) error {\n\tctx, span := obs.Observer.NewSpan(ctx, obs.SpanKindInternal, \"controller.flowWorker.switchProvider\")\n\tdefer span.End()\n\n\tif prv == nil {\n\t\treturn nil // no provider to switch to\n\t}\n\n\tlogger := fw.logger.WithFields(logrus.Fields{\n\t\t\"new_provider_name\": prv.Name().String(),\n\t\t\"new_provider_type\": prv.Type().String(),\n\t})\n\n\tchanged, tcIDTemplate, err := fw.flowCtx.Provider.SetProvider(ctx, prv)\n\tif err != nil {\n\t\tlogger.WithError(err).Error(\"failed to set provider\")\n\t\treturn fmt.Errorf(\"failed to set provider: %w\", err)\n\t}\n\n\tif !changed {\n\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,","sourceCodeStart":887,"sourceCodeEnd":923,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/flow.go#L887-L923","documentation":"flowWorker.switchProvider wraps any failure from fw.flowCtx.Provider.SetProvider(ctx, prv) — the in-memory runtime swap of the flow's LLM provider — with this message. SetProvider decides whether the provider actually changed and resolves the tool_call_id_template; an error here means the running flow could not adopt the new provider and keeps operating with the previous one. The wrapped underlying error carries the real cause (e.g. the new provider is unhealthy or rejected).","triggerScenarios":"A runtime provider switch is requested for a live flow (user changes the model/provider via UI or API) and SetProvider returns an error — typically because the new provider instance fails its initialization/validation, the provider cannot serve the flow's agent options, or an internal consistency check in the provider set fails.","commonSituations":"User switches to a provider whose API key/server URL is unset or wrong; provider was configured but its endpoint is unreachable; switching to a custom/user provider that shadows a built-in name with an invalid config; environment changed after the flow started so the new provider's config is now invalid.","solutions":["Inspect the wrapped cause in the log line 'failed to set provider' to see which step failed","Verify the target provider's config (API key, server URL, model) is valid and non-empty in Settings or env vars","Test the provider with a trivial call before switching a live flow","Retry the switch after correcting the provider configuration"],"exampleFix":"// before: switch fails at runtime with opaque provider\nprv, err := providerCtrl.GetProvider(ctx, cfgName)\n_ = worker.switchProvider(ctx, prv)\n// after: validate provider availability before switching\nprv, err := providerCtrl.GetProvider(ctx, cfgName)\nif err != nil { return err }\nif _, err := prv.Model(pconfig.OptionsTypePrimaryAgent); err != nil {\n    return fmt.Errorf(\"provider %s misconfigured: %w\", prv.Name(), err)\n}\nreturn worker.switchProvider(ctx, prv)","handlingStrategy":"try-catch","validationCode":"func canSwitch(prv provider.Provider) error {\n    if prv == nil { return errors.New(\"provider is nil\") }\n    if _, err := prv.Model(pconfig.OptionsTypePrimaryAgent); err != nil {\n        return fmt.Errorf(\"primary model unavailable: %w\", err)\n    }\n    return nil\n}","typeGuard":"func isSwitchable(prv provider.Provider) bool { return prv != nil }","tryCatchPattern":"changed, tpl, err := fw.flowCtx.Provider.SetProvider(ctx, prv)\nif err != nil {\n    var cfgErr *provider.ConfigError\n    if errors.As(err, &cfgErr) {\n        return fmt.Errorf(\"provider %s misconfigured, keeping current: %w\", prv.Name(), err)\n    }\n    return fmt.Errorf(\"failed to set provider: %w\", err)\n}","preventionTips":["Validate provider config (API key, URL, model) in Settings before offering it for a live switch","Test a provider with a minimal call at configuration time","Never pass a nil provider; switchProvider silently no-ops on nil","Watch the logrus log line 'failed to set provider' for the wrapped root cause"],"tags":["go","provider-switch","llm","runtime"],"backgroundTag":"invalid-provider-config","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}