fix(providers): skip unavailable user providers instead of failing the whole list

A saved user provider whose type is no longer enabled (e.g. its API key was
removed) made GetProviders return an error for the ENTIRE `providers` query —
one stale row blocked all flow creation in the UI ("No available providers").
Skip and log such rows, mirroring how startup already tolerates disabled
default providers. Pre-existing robustness gap, not introduced by this branch.

Verified live on the docker stand: the `providers` query went from a hard
error to returning all 10 enabled providers (the stale minimax row skipped),
and flow creation works again. Adds a side-by-side regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-06-27 22:10:40 +07:00
co-authored by Claude Opus 4.8
parent c752bb2737
commit 08a24c9abd
2 changed files with 53 additions and 1 deletions
@@ -0,0 +1,49 @@
package providers
import (
"context"
"testing"
"pentagi/pkg/config"
"pentagi/pkg/database"
"pentagi/pkg/providers/provider"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// stubTypedProvider reports only its type — enough for Providers.ListTypes(),
// which is all GetProviders touches before it rejects an unavailable type.
type stubTypedProvider struct {
provider.Provider
ptype provider.ProviderType
}
func (s stubTypedProvider) Type() provider.ProviderType { return s.ptype }
type stubProvidersQuerier struct {
database.Querier
rows []database.Provider
}
func (s stubProvidersQuerier) GetUserProviders(context.Context, int64) ([]database.Provider, error) {
return s.rows, nil
}
func TestGetProviders_SkipsUserProviderOfDisabledType(t *testing.T) {
pc := &providerController{
cfg: &config.Config{},
db: stubProvidersQuerier{rows: []database.Provider{
{Name: "stale-minimax", Type: "minimax"}, // not in ListTypes() below
}},
Providers: provider.Providers{
"openai-default": stubTypedProvider{ptype: provider.ProviderOpenAI},
},
}
got, err := pc.GetProviders(context.Background(), 1)
require.NoError(t, err, "one user provider of a disabled type must not fail the whole query")
assert.Contains(t, got, provider.ProviderName("openai-default"), "enabled providers stay reachable")
assert.NotContains(t, got, provider.ProviderName("stale-minimax"), "the unavailable provider is skipped")
}
+4 -1
View File
@@ -613,7 +613,10 @@ func (pc *providerController) GetProviders(
for _, prv := range providers {
p, err := pc.NewProvider(prv)
if err != nil {
return nil, fmt.Errorf("failed to build provider: %w", err)
// A saved provider of a now-disabled type must not fail the whole list —
// skip it (like startup tolerates disabled defaults) so the rest stay usable.
logrus.WithError(err).Warnf("skipping user provider '%s' of unavailable type '%s'", prv.Name, prv.Type)
continue
}
providersMap[provider.ProviderName(prv.Name)] = p
}