Skip to content

Commit da215b3

Browse files
authored
fix: backfill legacy Bedrock AI provider rows and stale model config strings (#26155) (#26453)
Fixes CODAGT-548 Adds two idempotent startup backfills run after `newAPI(): - `BackfillBedrockProviderType`: promotes `ai_providers` rows from `type=anthropic` with Bedrock settings to `type=bedrock`. - `BackfillChatModelConfigProviderStrings`: fixes stale `chat_model_configs.provider = "anthropic"` strings on rows whose linked provider was just promoted. - `UpdateAIProvider` query now also writes the `type` column, so the fix persists on any subsequent PATCH. > 🤖 Generated by Claude with oversight from a human. (cherry picked from commit a4c867f)
1 parent bcb3057 commit da215b3

18 files changed

Lines changed: 683 additions & 10 deletions

cli/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,11 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
10271027
); err != nil {
10281028
return xerrors.Errorf("seed ai providers from env: %w", err)
10291029
}
1030+
// Must run after newAPI so options.Database is dbcrypt-wrapped.
1031+
coderd.BackfillBedrockProviderType(aibridgeInitCtx, options.Database, logger.Named("aibridge.backfill"))
1032+
// Must run after BackfillBedrockProviderType; shares aibridgeInitCtx so
1033+
// a timeout on the first backfill will skip this one until next startup.
1034+
coderd.BackfillChatModelConfigProviderStrings(aibridgeInitCtx, options.Database, logger.Named("aibridge.backfill"))
10301035

10311036
// In-memory aibridge daemon. Registered on coderd so chatd can
10321037
// dispatch LLM requests via the in-process transport without

coderd/ai_providers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ func (api *API) aiProvidersUpdate(rw http.ResponseWriter, r *http.Request) {
351351
}
352352
params := database.UpdateAIProviderParams{
353353
ID: old.ID,
354+
Type: old.Type,
354355
DisplayName: displayName,
355356
Enabled: ptr.NilToDefault(req.Enabled, old.Enabled),
356357
BaseUrl: ptr.NilToDefault(req.BaseURL, old.BaseUrl),

coderd/ai_providers_backfill.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package coderd
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
8+
"cdr.dev/slog/v3"
9+
"github.com/coder/coder/v2/coderd/database"
10+
"github.com/coder/coder/v2/coderd/database/db2sdk"
11+
"github.com/coder/coder/v2/coderd/database/dbauthz"
12+
"github.com/coder/coder/v2/codersdk"
13+
)
14+
15+
// BackfillBedrockProviderType promotes legacy ai_providers rows stored as
16+
// type=anthropic with Bedrock settings to type=bedrock. Must run after newAPI
17+
// so options.Database is dbcrypt-wrapped. Idempotent; errors are logged and
18+
// startup continues.
19+
//
20+
// BackfillChatModelConfigProviderStrings must run after this function so
21+
// provider types are correct when its JOIN executes.
22+
func BackfillBedrockProviderType(ctx context.Context, db database.Store, logger slog.Logger) {
23+
//nolint:gocritic // Startup-only backfill; no user actor is present.
24+
sysCtx := dbauthz.AsSystemRestricted(ctx)
25+
providers, err := db.GetAIProviders(sysCtx, database.GetAIProvidersParams{
26+
IncludeDeleted: false,
27+
IncludeDisabled: true,
28+
})
29+
if err != nil {
30+
logger.Error(ctx, "backfill bedrock provider type: list providers", slog.Error(err))
31+
return
32+
}
33+
var promoted int
34+
for _, provider := range providers {
35+
if provider.Type != database.AiProviderTypeAnthropic {
36+
continue
37+
}
38+
settings, err := db2sdk.AIProviderSettings(provider.Settings)
39+
if err != nil {
40+
logger.Warn(ctx, "backfill bedrock provider type: skip provider with unparsable settings",
41+
slog.F("provider_id", provider.ID), slog.Error(err))
42+
continue
43+
}
44+
if settings.Bedrock == nil {
45+
continue
46+
}
47+
_, err = db.UpdateAIProvider(sysCtx, database.UpdateAIProviderParams{
48+
ID: provider.ID,
49+
Type: database.AiProviderTypeBedrock,
50+
DisplayName: provider.DisplayName,
51+
Enabled: provider.Enabled,
52+
BaseUrl: provider.BaseUrl,
53+
Settings: provider.Settings,
54+
// SettingsKeyID is re-set by the dbcrypt wrapper on write.
55+
SettingsKeyID: sql.NullString{},
56+
})
57+
if err != nil {
58+
if errors.Is(err, sql.ErrNoRows) {
59+
logger.Debug(ctx, "backfill bedrock provider type: provider deleted during backfill",
60+
slog.F("provider_id", provider.ID))
61+
continue
62+
}
63+
logger.Error(ctx, "backfill bedrock provider type: provider update failed and will re-attempt on next server startup",
64+
slog.F("provider_id", provider.ID), slog.Error(err))
65+
continue
66+
}
67+
promoted++
68+
}
69+
if promoted > 0 {
70+
logger.Info(ctx, "backfilled bedrock provider types", slog.F("count", promoted))
71+
}
72+
}
73+
74+
// BackfillChatModelConfigProviderStrings fixes stale chat_model_configs.provider
75+
// strings left as "anthropic" when the linked provider was promoted from
76+
// type=anthropic to type=bedrock by BackfillBedrockProviderType. Errors are
77+
// logged and startup continues.
78+
func BackfillChatModelConfigProviderStrings(ctx context.Context, db database.Store, logger slog.Logger) {
79+
//nolint:gocritic // Startup-only backfill; no user actor is present.
80+
sysCtx := dbauthz.AsSystemRestricted(ctx)
81+
result, err := db.BackfillChatModelConfigProvider(sysCtx, database.BackfillChatModelConfigProviderParams{
82+
OldProvider: string(codersdk.AIProviderTypeAnthropic),
83+
NewProvider: string(codersdk.AIProviderTypeBedrock),
84+
})
85+
if err != nil {
86+
logger.Error(ctx, "backfill chat model config provider strings", slog.Error(err))
87+
return
88+
}
89+
if result != nil {
90+
if n, _ := result.RowsAffected(); n > 0 {
91+
logger.Info(ctx, "backfilled chat model config provider strings", slog.F("count", n))
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)