Skip to content

Implicit provider auto-discovery silently merges with explicit config — can cause unexpected costs and routing #33327

@bhalliburton

Description

@bhalliburton

Summary

When models.providers is explicitly configured, OpenClaw still runs implicit provider auto-detection via environment variables and merges the results into the provider list. This can silently route traffic to unintended providers, cause confusing startup errors, and create cost/compliance surprises.

Behavior

Scenario 1: Confusing startup error

User configures models through a LiteLLM proxy (litellm/bedrock-* model IDs). They set AWS_PROFILE in their shell for an unrelated tool (Claude Code). On openclaw gateway restart:

[bedrock-discovery] Failed to list models: UnrecognizedClientException: The security token included in the request is invalid.

OpenClaw detects AWS_PROFILE and tries native Bedrock model discovery, even though the user routes Bedrock traffic through LiteLLM.

Scenario 2: Silent cost leak

User configures models through a proxy for cost tracking and compliance. They also have ANTHROPIC_API_KEY set in their environment (for another tool, or left over from initial setup). If the proxy goes down or auth fails, resolveApiKeyForProvider() walks the auth chain and finds ANTHROPIC_API_KEY at step 4 (env var fallback) — silently routing traffic directly to the Anthropic API, bypassing the proxy, cost tracking, and audit logging.

Root Cause

resolveProvidersForModelsJson() in src/agents/models-config.ts merges implicit + explicit providers regardless of whether the user has configured explicit providers:

const providers = mergeProviders({
    implicit: await resolveImplicitProviders({ agentDir, explicitProviders }),
    explicit: explicitProviders
});
// Then ALSO unconditionally adds bedrock and copilot
const implicitBedrock = await resolveImplicitBedrockProvider(...)
if (implicitBedrock) { providers["amazon-bedrock"] = ... }
const implicitCopilot = await resolveImplicitCopilotProvider(...)
if (implicitCopilot) { providers["github-copilot"] = ... }

Additionally, resolveApiKeyForProvider() in src/agents/model-selection.ts falls back to env vars at request time:

// Auth resolution order:
// 1. Explicit profile
// 2. Provider auth override from config  
// 3. Auth profile store
// 4. Environment variables  ← ANTHROPIC_API_KEY, OPENAI_API_KEY, etc. picked up here
// 5. Custom key from models.json

This means env vars act as a silent fallback even when the user has configured a specific auth path for a provider.

Scope

This is not limited to Bedrock. resolveImplicitProviders() sniffs 20+ env vars for implicit provider registration:

Env var Provider Startup behavior
AWS_PROFILE / AWS_ACCESS_KEY_ID Bedrock API call (ListFoundationModels) — can error
GH_TOKEN / GITHUB_TOKEN Copilot API call — can error
HF_TOKEN HuggingFace Discovery call — can error
ANTHROPIC_API_KEY Anthropic Passive (silent fallback at request time)
OPENAI_API_KEY OpenAI Passive
GEMINI_API_KEY Google Passive
OLLAMA_API_KEY Ollama Probes local server
+ ~15 more (Together, OpenRouter, Venice, etc.) Various Passive

Common env vars like AWS_PROFILE, GH_TOKEN, and GEMINI_API_KEY are frequently set for unrelated tools.

Suggested Fix

  • When models.providers is empty/missing → auto-detect everything (current behavior, great for zero-config onboarding)
  • When models.providers has entries → use exactly what is configured. Do not merge implicit providers. Do not fall back to env vars for auth on configured providers.

Workaround

Until fixed, users with explicit provider configs can either:

  • Add "bedrockDiscovery": { "enabled": false } under models in config (suppresses Bedrock specifically)
  • Scope conflicting env vars so they do not leak into the gateway process (e.g., alias-scoped vars instead of global exports)

Environment

  • OpenClaw: 2026.3.2
  • OS: macOS 15.3.2 (arm64)
  • Config: LiteLLM proxy with explicit models.providers containing litellm/bedrock-* model routing
  • Trigger: AWS_PROFILE set globally for Claude Code Bedrock integration

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions