fix(workspace): project Anthropic adapter creds for BYOK MiniMax on claude-code (core#2709) #2711

Merged
devops-engineer merged 1 commits from fix/2709-claude-code-minimax-auth-projection into main 2026-06-13 06:05:40 +00:00
2 changed files with 77 additions and 1 deletions
@@ -1163,6 +1163,28 @@ func applyPlatformManagedLLMEnv(ctx context.Context, envVars map[string]string,
// workspace-key, reno's own oauth). Only the inherited operator-store
// channel is provider-gated.
stripNonMatchingGlobalOriginLLMCreds(envVars, globalKeys, runtime, effectiveModel, availableAuthEnv)
// core#2709: claude-code's Anthropic SDK adapter reads ANTHROPIC_AUTH_TOKEN
// and ANTHROPIC_BASE_URL. A BYOK MiniMax workspace arrives here with
// MINIMAX_API_KEY but no Anthropic-shaped creds, so the adapter 401s after
// restart. Project the provider's preferred auth token env and Anthropic
// base URL from the workspace's available provider credential.
if res.ResolvedMode == LLMBillingModeBYOK && runtimeUsesAnthropicNativeProxy(runtime) {
if provider, ok := providerFromRegistry(derefOrEmpty(res.ProviderSelection)); ok && provider.AuthTokenEnv != "" {
if _, hasToken := envVars[provider.AuthTokenEnv]; !hasToken {
for _, authEnv := range provider.AuthEnv {
if v := strings.TrimSpace(envVars[authEnv]); v != "" {
envVars[provider.AuthTokenEnv] = v
break
}
}
}
if _, hasBase := envVars["ANTHROPIC_BASE_URL"]; !hasBase && provider.BaseURLAnthropic != "" {
envVars["ANTHROPIC_BASE_URL"] = provider.BaseURLAnthropic
}
}
}
return platformLLMEnvResult{
ResolvedMode: res.ResolvedMode,
HasUsableLLMCred: hasAnyPlatformManagedLLMKey(envVars),
@@ -1199,7 +1221,15 @@ func applyPlatformManagedLLMEnv(ctx context.Context, envVars map[string]string,
envVars["OPENAI_BASE_URL"] = baseURL
}
if runtimeUsesAnthropicNativeProxy(runtime) && anthropicBaseURL != "" {
envVars["ANTHROPIC_API_KEY"] = token
// core#2709: use the resolved provider's auth_token_env instead of
// hardcoding ANTHROPIC_API_KEY. MiniMax's Anthropic-compatible endpoint
// expects ANTHROPIC_AUTH_TOKEN, while the platform proxy surface expects
// ANTHROPIC_API_KEY.
anthropicTokenEnv := "ANTHROPIC_API_KEY"
if provider, ok := providerFromRegistry(derefOrEmpty(res.ProviderSelection)); ok && provider.AuthTokenEnv != "" {
anthropicTokenEnv = provider.AuthTokenEnv
}
envVars[anthropicTokenEnv] = token
envVars["ANTHROPIC_BASE_URL"] = anthropicBaseURL
// CP#752 WS1b: claude-code uses the Anthropic CLI/SDK's
// ANTHROPIC_CUSTOM_HEADERS env var to attach per-workspace
@@ -1311,6 +1341,26 @@ func runtimeUsesAnthropicNativeProxy(runtime string) bool {
return strings.EqualFold(strings.TrimSpace(runtime), "claude-code")
}
// providerFromRegistry looks up a provider by name in the cached embedded
// providers manifest. It returns the provider and true if found. Used by
// applyPlatformManagedLLMEnv to project the adapter-specific auth env / base
// URL (e.g. ANTHROPIC_AUTH_TOKEN for MiniMax on claude-code).
func providerFromRegistry(name string) (providers.Provider, bool) {
if name == "" {
return providers.Provider{}, false
}
manifest, err := providerRegistry()
if err != nil || manifest == nil {
return providers.Provider{}, false
}
for _, p := range manifest.Providers {
if strings.EqualFold(p.Name, name) {
return p, true
}
}
return providers.Provider{}, false
}
func firstNonEmptyEnv(names ...string) string {
for _, name := range names {
if v := strings.TrimSpace(os.Getenv(name)); v != "" {
@@ -1100,6 +1100,32 @@ func TestApplyPlatformManagedLLMEnv_ClaudeCodeInjectsAnthropicProxyWhenNoWorkspa
}
}
// TestApplyPlatformManagedLLMEnv_BYOKMiniMaxProjectsAnthropicAdapterCreds is
// core#2709: a claude-code workspace using BYOK MiniMax arrives with only
// MINIMAX_API_KEY. The Anthropic SDK adapter needs ANTHROPIC_AUTH_TOKEN and
// ANTHROPIC_BASE_URL to authenticate against api.minimax.io/anthropic.
func TestApplyPlatformManagedLLMEnv_BYOKMiniMaxProjectsAnthropicAdapterCreds(t *testing.T) {
envVars := map[string]string{
"MINIMAX_API_KEY": "user-minimax-key",
"MODEL": "MiniMax-M2.7",
}
res := applyPlatformManagedLLMEnv(context.Background(), envVars, "", "claude-code", "MiniMax-M2.7", nil)
if res.ResolvedMode != LLMBillingModeBYOK {
t.Fatalf("resolved mode = %q, want byok", res.ResolvedMode)
}
if got := envVars["ANTHROPIC_AUTH_TOKEN"]; got != "user-minimax-key" {
t.Fatalf("ANTHROPIC_AUTH_TOKEN = %q, want user-minimax-key", got)
}
if got := envVars["ANTHROPIC_BASE_URL"]; got != "https://api.minimax.io/anthropic/v1" {
t.Fatalf("ANTHROPIC_BASE_URL = %q, want https://api.minimax.io/anthropic/v1", got)
}
// The original MiniMax key is preserved too.
if got := envVars["MINIMAX_API_KEY"]; got != "user-minimax-key" {
t.Fatalf("MINIMAX_API_KEY was overwritten: %q", got)
}
}
func TestApplyPlatformManagedLLMEnv_ClaudeCodeStripsVendorBYOK(t *testing.T) {
t.Setenv("MOLECULE_LLM_BILLING_MODE", "platform_managed")
t.Setenv("MOLECULE_LLM_BASE_URL", "https://api.example.test/api/v1/internal/llm/openai/v1")