From faae96ccc2f365c8773c17d00df6c82d57ac9a9b Mon Sep 17 00:00:00 2001 From: Raymond Gasper Date: Mon, 8 Jun 2026 09:09:18 -0400 Subject: [PATCH] fix #15: use all knownProviders in fallback chain for environment variable auth When resolving credentials, if the default provider (anthropic) has no credentials configured, Resolve tries to fall back to other providers that DO have credentials available. This is crucial for environment variable authentication to work. The bug: the fallback list was hardcoded to only check a subset of providers: - anthropic, openai, openai-codex, kimi, deepseek, google This meant that providers like amazon-bedrock, github-copilot, google-vertex, and others were never checked for environment variable credentials. When ZOT_HOME was set to a new location (with no config.json), the default provider would be anthropic, and without anthropic credentials set, the fallback would skip bedrock (and other missing providers), causing the 'you are not logged in' dialog to appear. The fix: replace the hardcoded list with knownProviders, which contains all 40+ supported providers. Now all providers are checked for available credentials, regardless of ZOT_HOME setting. Added two tests: - TestResolveEnvironmentVariableAuthWithoutConfiguredProvider: verifies bedrock credentials via environment variables are found - TestResolveFallsBackThroughAllKnownProviders: verifies the fallback mechanism checks all known providers, not just a hardcoded subset --- packages/agent/build.go | 4 +-- packages/agent/build_test.go | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/agent/build.go b/packages/agent/build.go index 4271333..15e8ef0 100644 --- a/packages/agent/build.go +++ b/packages/agent/build.go @@ -317,8 +317,8 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { // never shows a "not logged in" banner. userPickedProvider := args.Provider != "" if credErr != nil && !userPickedProvider && provName != "ollama" { - for _, other := range []string{"anthropic", "openai", "openai-codex", "kimi", "deepseek", "google"} { - if other == provName { + for _, other := range knownProviders { + if other == provName || other == "ollama" { continue } if c, m, a, err := ResolveCredentialFull(other, args.APIKey); err == nil { diff --git a/packages/agent/build_test.go b/packages/agent/build_test.go index eb8e37e..7a8dec0 100644 --- a/packages/agent/build_test.go +++ b/packages/agent/build_test.go @@ -180,3 +180,61 @@ func TestCanonicalProviderAliasesAreKnown(t *testing.T) { } } } + +// TestResolveEnvironmentVariableAuthWithoutConfiguredProvider reproduces +// the bug where setting ZOT_HOME breaks environment variable authentication. +// When no provider is configured and the default provider (anthropic) has no +// credentials, Resolve should fall back to ANY provider that has credentials +// via environment variables, including amazon-bedrock and other providers. +func TestResolveEnvironmentVariableAuthWithoutConfiguredProvider(t *testing.T) { + t.Setenv("ZOT_HOME", t.TempDir()) + // No ANTHROPIC credentials set + t.Setenv("ANTHROPIC_API_KEY", "") + t.Setenv("ANTHROPIC_OAUTH_TOKEN", "") + t.Setenv("OPENAI_API_KEY", "") + + // Test case 1: amazon-bedrock via environment variables + t.Setenv("AWS_BEARER_TOKEN_BEDROCK", "test-token-123") + t.Setenv("AWS_REGION", "us-east-1") + + // No config.json created yet (fresh ZOT_HOME) + // Resolve should NOT error and should detect bedrock credentials + r, err := Resolve(Args{}, false) + if err != nil { + t.Fatalf("Resolve failed with bedrock env vars set: %v", err) + } + if r.Provider != "amazon-bedrock" { + t.Errorf("Resolve with bedrock env var: got provider %q, want amazon-bedrock", r.Provider) + } + if !r.HasCredential() { + t.Errorf("Resolve should have found bedrock credential from environment variable") + } +} + +// TestResolveFallsBackThroughAllKnownProviders ensures that when the +// default provider has no credentials, Resolve tries all known providers +// (not just a hardcoded subset) to find one with available credentials. +// We test with github-copilot which was NOT in the old hardcoded list. +func TestResolveFallsBackThroughAllKnownProviders(t *testing.T) { + t.Setenv("ZOT_HOME", t.TempDir()) + // Clear anthropic credentials + t.Setenv("ANTHROPIC_API_KEY", "") + t.Setenv("ANTHROPIC_OAUTH_TOKEN", "") + // Clear openai credentials + t.Setenv("OPENAI_API_KEY", "") + // Clear amazon-bedrock (since it comes first in knownProviders) + t.Setenv("AWS_BEARER_TOKEN_BEDROCK", "") + t.Setenv("AWS_ACCESS_KEY_ID", "") + t.Setenv("AWS_PROFILE", "") + + // Set github-copilot, which was NOT in the old hardcoded list + t.Setenv("COPILOT_GITHUB_TOKEN", "copilot-token-789") + + r, err := Resolve(Args{}, false) + if err != nil { + t.Fatalf("Resolve failed: %v", err) + } + if r.Provider != "github-copilot" { + t.Errorf("Resolve should have found github-copilot in fallback chain, got %q", r.Provider) + } +}