feat: warn instead of fail on deprecated AI provider env drift

Deprecated CODER_AIBRIDGE_* env configuration that drifts from the
existing ai_providers DB row no longer fails startup.
SeedAIProvidersFromEnv now leaves the stored row untouched, logs a
warning, and returns an "ineffective" flag. The flag is stored on the
API via an atomic so the appearance layer can later surface a banner to
admins. Other missing providers in the same config are still inserted.
This commit is contained in:
Danny Kopping
2026-06-01 09:22:22 +00:00
parent 76d3181aba
commit 2716632a51
6 changed files with 181 additions and 70 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ func buildFromEnv(t *testing.T, cfg codersdk.AIBridgeConfig) ([]aibridge.Provide
db, _ := dbtestutil.NewDB(t)
ctx := testutil.Context(t, testutil.WaitShort)
logger := slogtest.Make(t, nil)
if err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, logger); err != nil {
if _, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, logger); err != nil {
return nil, err
}
providers, _, err := BuildProviders(ctx, db, cfg, logger)
+7 -2
View File
@@ -1024,14 +1024,19 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
//nolint:gocritic // Production timeout, not a test wait.
aibridgeInitCtx, aibridgeInitCancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second)
defer aibridgeInitCancel()
if err := coderd.SeedAIProvidersFromEnv(
ineffective, err := coderd.SeedAIProvidersFromEnv(
aibridgeInitCtx,
options.Database,
vals.AI.BridgeConfig,
logger.Named("aibridge.envseed"),
); err != nil {
)
if err != nil {
return xerrors.Errorf("seed ai providers from env: %w", err)
}
// This runs unconditionally for both editions (enterprise
// reuses this AGPL server runner), so it is the authoritative
// setter for the deprecated-env-drift warning banner.
coderAPI.AIProvidersEnvDrift.Store(ineffective)
// In-memory aibridge daemon. Registered on coderd so chatd can
// dispatch LLM requests via the in-process transport without
+38 -9
View File
@@ -26,9 +26,16 @@ import (
// derived AI provider configuration with rows in the ai_providers
// table at server startup. Concurrent server starts are serialized via a
// Postgres advisory lock; rows that already exist with a matching
// canonical hash are left alone, missing rows are inserted, and rows
// whose hash differs from the env-derived value cause startup to fail
// with a descriptive error.
// canonical hash are left alone and missing rows are inserted.
//
// AI-provider configuration via CODER_AIBRIDGE_* env vars is deprecated
// in favor of managing providers through the API/UI, where the database
// is the source of truth. When an env-derived provider differs from the
// row already stored in the database ("drift"), startup does NOT fail:
// the env change is ineffective, so the existing row is left untouched, a
// warning is logged, and the returned ineffective flag is set to true so
// callers can surface a banner to admins. Other missing providers in the
// same config are still inserted.
//
// API keys derived from env vars are inserted into ai_provider_keys at
// the time the provider row is first created. We do NOT add env-sourced
@@ -45,13 +52,13 @@ func SeedAIProvidersFromEnv(
db database.Store,
cfg codersdk.AIBridgeConfig,
logger slog.Logger,
) error {
) (ineffective bool, err error) {
desired, err := providersFromEnv(ctx, cfg, logger)
if err != nil {
return xerrors.Errorf("compute providers from env: %w", err)
return false, xerrors.Errorf("compute providers from env: %w", err)
}
if len(desired) == 0 {
return nil
return false, nil
}
// Audit entries are attributed to the deployment rather than a user.
@@ -65,11 +72,19 @@ func SeedAIProvidersFromEnv(
var (
insertedProviders []database.AIProvider
insertedKeys []database.AIProviderKey
// drift records whether any env-derived provider differs from a
// row already in the database. driftedNames collects the affected
// provider names for the warning log. Both are reset at the top of
// the InTx closure so a transaction retry recomputes them cleanly.
drift bool
driftedNames []string
)
err = db.InTx(func(tx database.Store) error {
insertedProviders = insertedProviders[:0]
insertedKeys = insertedKeys[:0]
drift = false
driftedNames = driftedNames[:0]
// Acquire the advisory lock. The lock is released when the
// transaction ends.
@@ -136,7 +151,16 @@ func SeedAIProvidersFromEnv(
if existingHash == dp.Hash {
continue
}
return xerrors.Errorf("AI provider %q already exists in the database and differs from the current environment configuration; update the provider through the API or remove the CODER_AIBRIDGE_* env vars to stop seeding it", dp.Name)
// The env-derived config drifted from the stored row.
// CODER_AIBRIDGE_* env config is deprecated, so this is
// not fatal: the database is the source of truth, the
// existing row is left untouched, and the env change is
// ineffective. Flag it (the warning is emitted after the
// transaction commits, like the insert logs below) and keep
// reconciling the remaining providers.
drift = true
driftedNames = append(driftedNames, dp.Name)
continue
}
row, err := tx.InsertAIProvider(sysCtx, database.InsertAIProviderParams{
@@ -183,7 +207,7 @@ func SeedAIProvidersFromEnv(
return nil
}, nil)
if err != nil {
return err
return false, err
}
for _, row := range insertedProviders {
@@ -201,7 +225,12 @@ func SeedAIProvidersFromEnv(
slog.F("api_key", aibridgeutils.MaskSecret(keyRow.APIKey)),
)
}
return nil
if drift {
logger.Warn(sysCtx, "deprecated AI provider env configuration is ineffective; the database is the source of truth, so update these providers through the API or remove their CODER_AIBRIDGE_* env vars",
slog.F("providers", driftedNames),
)
}
return drift, nil
}
// canonicalAIProvider is the shape we hash to detect drift between the
+122 -56
View File
@@ -24,8 +24,9 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
t.Parallel()
db, _ := dbtestutil.NewDB(t)
ctx := testutil.Context(t, testutil.WaitShort)
err := coderd.SeedAIProvidersFromEnv(ctx, db, codersdk.AIBridgeConfig{}, testLogger(t))
ineffective, err := coderd.SeedAIProvidersFromEnv(ctx, db, codersdk.AIBridgeConfig{}, testLogger(t))
require.NoError(t, err)
require.False(t, ineffective)
})
t.Run("LegacyOpenAI", func(t *testing.T) {
@@ -40,7 +41,7 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
}
var firstSeedLogs bytes.Buffer
err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, capturedLogger(&firstSeedLogs))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, capturedLogger(&firstSeedLogs))
require.NoError(t, err)
// One row exists for "openai".
@@ -65,7 +66,7 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
// Re-running with the same config is a no-op and emits no new
// env-seed log lines.
var rerunLogs bytes.Buffer
err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, capturedLogger(&rerunLogs))
_, err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, capturedLogger(&rerunLogs))
require.NoError(t, err)
require.NotContains(t, rerunLogs.String(), "env-seeded ai provider")
@@ -78,7 +79,7 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
require.Len(t, keys, 1)
})
t.Run("DriftFailsStartup", func(t *testing.T) {
t.Run("DriftIsNonFatalAndFlagged", func(t *testing.T) {
t.Parallel()
db, _ := dbtestutil.NewDB(t)
ctx := testutil.Context(t, testutil.WaitShort)
@@ -89,22 +90,38 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
Key: serpent.String("sk-original"),
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
ineffective, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
require.False(t, ineffective)
// Changing the API key counts as drift: keys are included
// in the canonical hash so operators notice when env-var
// credential changes are ignored by an existing provider.
// Changing the API key counts as drift: keys are included in the
// canonical hash. Because CODER_AIBRIDGE_* config is deprecated,
// drift no longer fails startup; the env change is ineffective,
// the stored row is left untouched, and the call reports it.
cfg.LegacyOpenAI.Key = serpent.String("sk-rotated")
err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "differs from the current environment configuration")
ineffective, err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
require.True(t, ineffective)
// Changing the base URL is also real drift.
// The database still holds the original key.
row, err := db.GetAIProviderByName(ctx, "openai")
require.NoError(t, err)
keys, err := db.GetAIProviderKeysByProviderID(ctx, row.ID)
require.NoError(t, err)
require.Len(t, keys, 1)
require.Equal(t, "sk-original", keys[0].APIKey)
// Changing the base URL is also drift, and also non-fatal.
cfg.LegacyOpenAI.Key = serpent.String("sk-original")
cfg.LegacyOpenAI.BaseURL = serpent.String("https://api.openai.com/v2")
err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "differs from the current environment configuration")
ineffective, err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
require.True(t, ineffective)
// The stored base URL is unchanged.
row, err = db.GetAIProviderByName(ctx, "openai")
require.NoError(t, err)
require.Equal(t, "https://api.openai.com/v1", row.BaseUrl)
})
t.Run("BedrockCredentialChangeIsDrift", func(t *testing.T) {
@@ -120,24 +137,35 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
Model: serpent.String("anthropic.claude-3-5-sonnet"),
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
// Rotating the Bedrock access key in env trips the drift
// check so operators know the change did not take effect.
// Rotating the Bedrock access key in env is drift. It is
// non-fatal for the deprecated env path: the call reports the
// change is ineffective and the stored credentials are unchanged.
cfg.LegacyBedrock.AccessKey = serpent.String("AKIA-rotated")
cfg.LegacyBedrock.AccessKeySecret = serpent.String("secret-rotated")
err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "differs from the current environment configuration")
ineffective, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
require.True(t, ineffective)
// Changing the Bedrock region (a non-credential field) is
// also real drift.
row, err := db.GetAIProviderByName(ctx, "anthropic")
require.NoError(t, err)
require.Contains(t, row.Settings.String, "AKIA-original")
require.Contains(t, row.Settings.String, "secret-original")
// Changing the Bedrock region (a non-credential field) is also
// drift, and also non-fatal.
cfg.LegacyBedrock.AccessKey = serpent.String("AKIA-original")
cfg.LegacyBedrock.AccessKeySecret = serpent.String("secret-original")
cfg.LegacyBedrock.Region = serpent.String("us-west-2")
err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "differs from the current environment configuration")
ineffective, err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
require.True(t, ineffective)
row, err = db.GetAIProviderByName(ctx, "anthropic")
require.NoError(t, err)
require.Contains(t, row.Settings.String, "us-east-1")
})
t.Run("LegacyBedrockOnlyKeepsBedrockSettings", func(t *testing.T) {
@@ -156,7 +184,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
SmallFastModel: serpent.String("anthropic.claude-3-5-haiku"),
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
row, err := db.GetAIProviderByName(ctx, "anthropic")
require.NoError(t, err)
@@ -191,7 +220,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
cfg := dv.AI.BridgeConfig
cfg.LegacyAnthropic.Key = serpent.String("sk-ant-only")
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
row, err := db.GetAIProviderByName(ctx, "anthropic")
require.NoError(t, err)
@@ -216,7 +246,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
Model: serpent.String("anthropic.claude-3-5-sonnet"),
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
row, err := db.GetAIProviderByName(ctx, "anthropic")
require.NoError(t, err)
@@ -241,7 +272,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
Model: serpent.String("anthropic.claude-3-5-sonnet"),
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
row, err := db.GetAIProviderByName(ctx, "anthropic")
require.NoError(t, err)
require.Contains(t, row.Settings.String, "us-east-1")
@@ -275,7 +307,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
oa, err := db.GetAIProviderByName(ctx, "primary-openai")
require.NoError(t, err)
@@ -319,34 +352,55 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
// Reordering keys must not count as drift. The canonical hash
// sorts keys before hashing, so equivalent key sets remain
// stable across restarts.
cfg.Providers[0].Keys = []string{"sk-openai-2", "sk-openai-1"}
cfg.Providers[1].Keys = []string{"sk-ant-2", "sk-ant-1"}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
ineffective, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
require.False(t, ineffective)
// Changing one key on one provider must block startup even
// when multiple providers are configured.
// Changing one key on one provider is drift even when multiple
// providers are configured. Drift is non-fatal: it must not block
// a brand-new provider in the same config from being inserted,
// which exercises the continue-after-drift path.
cfg.Providers[1].Keys = []string{"sk-ant-2", "sk-ant-rotated"}
err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "differs from the current environment configuration")
require.Contains(t, err.Error(), `"primary-anthropic"`)
cfg.Providers = append(cfg.Providers, codersdk.AIProviderConfig{
Type: "openai",
Name: "secondary-openai",
BaseURL: "https://api.openai.com/v1",
Keys: []string{"sk-new"},
})
ineffective, err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
require.True(t, ineffective)
// The non-drifting provider is still present with its original keys.
oa, err := db.GetAIProviderByName(ctx, "primary-openai")
require.NoError(t, err)
oaKeys, err := db.GetAIProviderKeysByProviderID(ctx, oa.ID)
require.NoError(t, err)
require.ElementsMatch(t, []string{"sk-openai-1", "sk-openai-2"}, []string{oaKeys[0].APIKey, oaKeys[1].APIKey})
// The drifted provider keeps its original keys; the env change
// was ignored.
an, err := db.GetAIProviderByName(ctx, "primary-anthropic")
require.NoError(t, err)
anKeys, err := db.GetAIProviderKeysByProviderID(ctx, an.ID)
require.NoError(t, err)
require.ElementsMatch(t, []string{"sk-ant-1", "sk-ant-2"}, []string{anKeys[0].APIKey, anKeys[1].APIKey})
// The new provider was inserted despite the drift on another row.
sec, err := db.GetAIProviderByName(ctx, "secondary-openai")
require.NoError(t, err)
secKeys, err := db.GetAIProviderKeysByProviderID(ctx, sec.ID)
require.NoError(t, err)
require.Len(t, secKeys, 1)
require.Equal(t, "sk-new", secKeys[0].APIKey)
})
t.Run("BedrockIndexedProviderHasNoKeys", func(t *testing.T) {
@@ -367,7 +421,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
row, err := db.GetAIProviderByName(ctx, "bedrock-anthropic")
require.NoError(t, err)
@@ -398,7 +453,9 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
// Pre-transaction validation: a legacy/indexed name conflict is
// still fatal.
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "conflicts")
})
@@ -417,7 +474,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
// Pre-transaction validation: an invalid name is still fatal.
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "invalid AI provider name")
})
@@ -446,7 +504,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
all, err := db.GetAIProviders(ctx, database.GetAIProvidersParams{})
require.NoError(t, err)
@@ -465,7 +524,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
Key: serpent.String("sk-original"),
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
row, err := db.GetAIProviderByName(ctx, "openai")
require.NoError(t, err)
@@ -473,14 +533,15 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
// Re-run seed; the soft-deleted row should remain soft-deleted
// and no new row should be created.
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
all, err := db.GetAIProviders(ctx, database.GetAIProvidersParams{})
require.NoError(t, err)
require.Empty(t, all, "expected no active rows after soft-delete + re-seed")
})
t.Run("ExistingKeysBlockOnDrift", func(t *testing.T) {
t.Run("ExistingKeysDriftKeepsOriginalKeys", func(t *testing.T) {
t.Parallel()
db, _ := dbtestutil.NewDB(t)
ctx := testutil.Context(t, testutil.WaitShort)
@@ -491,17 +552,19 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
Key: serpent.String("sk-original"),
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
row, err := db.GetAIProviderByName(ctx, "openai")
require.NoError(t, err)
// Operator rotates the env key. The seed now blocks startup
// because the keys differ, alerting the operator.
// Operator rotates the env key. The seed reports the change is
// ineffective rather than blocking startup, and the database row
// is left untouched.
cfg.LegacyOpenAI.Key = serpent.String("sk-rotated")
err = coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "differs from the current environment configuration")
ineffective, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
require.True(t, ineffective)
// The original key is still in the database.
keys, err := db.GetAIProviderKeysByProviderID(ctx, row.ID)
@@ -533,7 +596,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
all, err := db.GetAIProviders(ctx, database.GetAIProvidersParams{})
require.NoError(t, err)
@@ -563,7 +627,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
require.NoError(t, coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t)))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.NoError(t, err)
all, err := db.GetAIProviders(ctx, database.GetAIProvidersParams{})
require.NoError(t, err)
@@ -579,7 +644,8 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
db, _ := dbtestutil.NewDB(t)
ctx := testutil.Context(t, testutil.WaitShort)
// Same name, different canonical fields: must be rejected.
// Same name, different canonical fields: pre-transaction
// validation rejects this, so it is still fatal.
cfg := codersdk.AIBridgeConfig{
Providers: []codersdk.AIProviderConfig{
{
@@ -596,7 +662,7 @@ func TestSeedAIProvidersFromEnv(t *testing.T) {
},
},
}
err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
_, err := coderd.SeedAIProvidersFromEnv(ctx, db, cfg, testLogger(t))
require.Error(t, err)
require.Contains(t, err.Error(), "conflicting fields")
})
+6
View File
@@ -2180,6 +2180,12 @@ type API struct {
WebpushDispatcher webpush.Dispatcher
QuotaCommitter atomic.Pointer[proto.QuotaCommitter]
AppearanceFetcher atomic.Pointer[appearance.Fetcher]
// AIProvidersEnvDrift is set to true at startup when deprecated
// CODER_AIBRIDGE_* env configuration differs from the AI provider rows
// already stored in the database. The appearance fetchers read it to
// warn admins that their env changes are ineffective. The zero value
// (false) needs no initialization.
AIProvidersEnvDrift atomic.Bool
// WorkspaceProxyHostsFn returns the hosts of healthy workspace proxies
// for header reasons.
WorkspaceProxyHostsFn atomic.Pointer[func() []*proxyhealth.ProxyHost]
+7 -2
View File
@@ -178,14 +178,19 @@ func (r *RootCmd) Server(_ func()) *serpent.Command {
//nolint:gocritic // Production timeout, not a test wait.
aibridgeInitCtx, aibridgeInitCancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second)
defer aibridgeInitCancel()
if err := agplcoderd.SeedAIProvidersFromEnv(
ineffective, err := agplcoderd.SeedAIProvidersFromEnv(
aibridgeInitCtx,
options.Database,
options.DeploymentValues.AI.BridgeConfig,
options.Logger.Named("aibridge.envseed"),
); err != nil {
)
if err != nil {
return nil, nil, xerrors.Errorf("seed ai providers from env: %w", err)
}
// Belt-and-suspenders: the agplcli re-seed after newAPI
// overwrites this with the same deterministic value. Use the
// computed flag rather than a hardcoded true.
api.AGPL.AIProvidersEnvDrift.Store(ineffective)
aiBridgeProxyCloser, err := newAIBridgeProxyDaemon(api)
if err != nil {
_ = closers.Close()