Skip to content

bug(model): catalog apiKeyEnvVar is ignored by credential resolution #339

Description

@MicroMilo

Summary

Built-in provider catalog entries declare apiKeyEnvVar, but parseModelConfig does not use that metadata when resolving provider credentials. As a result, a catalog provider/model can inherit catalog protocol, default URL, and model defaults, while still failing with missing_api_key even when the catalog-declared environment variable is set.

Code path

Producer / parser / validator / consumer anchors:

  • src/model/catalog/types.ts:12-17 defines CatalogProviderEntry.apiKeyEnvVar.
  • src/model/catalog/providers.ts:7-12 sets apiKeyEnvVar for anthropic as ANTHROPIC_API_KEY.
  • src/model/catalog/providers.ts:123-128 sets apiKeyEnvVar for openai as OPENAI_API_KEY.
  • src/model/config/parseModelConfig.ts:70-86 reads the catalog provider and uses catalog defaults for protocol/default URL.
  • src/model/config/parseModelConfig.ts:103-108 calls resolveApiKey(provider.apiKey, env) without passing catalogProvider.apiKeyEnvVar.
  • src/model/config/resolveCredentials.ts:18-26 rejects missing/non-string apiKey as missing_api_key.
  • src/model/config/resolveCredentials.ts:28-42 can resolve explicit ${ENV_VAR} strings from env.
  • src/model/streaming/streamModel.ts:515-532 sends the parsed provider.apiKey in provider request headers.

Steps to reproduce

Validation level: dynamic minimal reproduction plus source control-flow confirmation.

With ANTHROPIC_API_KEY available in the parser env, parse an Anthropic catalog provider/model without an explicit raw apiKey field:

parseModelConfig(
  {
    providers: {
      anthropic: {
        models: {
          "claude-sonnet-4-5-20250929": {},
        },
      },
    },
  },
  { env: { ANTHROPIC_API_KEY: "sk-test-from-env" } },
);

Observed output from a local tsx reproduction:

ERROR ModelConfigError missing_api_key Provider apiKey must be a non-empty string.

Control case with explicit env reference:

apiKey: "${ANTHROPIC_API_KEY}"

Observed output:

OK sk-test-from-env

Expected behavior

If catalog provider metadata is intended to provide runtime defaults, apiKeyEnvVar should be projected into credential resolution when provider.apiKey is absent. For example, anthropic could resolve from ANTHROPIC_API_KEY without requiring the user to repeat apiKey: ${ANTHROPIC_API_KEY} in config.

If apiKeyEnvVar is intended to be UI-only metadata rather than runtime credential metadata, the field should be renamed, documented, or otherwise kept out of the runtime-default path so it is not mistaken for an executable contract.

Actual behavior

parseModelConfig consumes adjacent catalog metadata for provider protocol/default URL/model defaults, but ignores catalogProvider.apiKeyEnvVar. The credential validator only receives raw provider.apiKey, so missing raw apiKey fails even when the catalog-declared env var is present.

Existing coverage

Related but not full coverage:

I also checked current issues/PRs for apiKeyEnvVar, resolveApiKey missing_api_key, ANTHROPIC_API_KEY, OPENAI_API_KEY, and parseModelConfig resolveCredentials; no same-root issue/PR was found.

Suggested fix

When provider.apiKey is absent and catalogProvider?.apiKeyEnvVar is set, pass an equivalent env reference into resolveApiKey, or add a small helper such as:

const rawApiKey = provider.apiKey ?? (catalogProvider?.apiKeyEnvVar ? `\${${catalogProvider.apiKeyEnvVar}}` : undefined);
apiKey: resolveApiKey(rawApiKey, env),

The exact shape can vary; the important part is that the catalog credential metadata and runtime credential parser agree on whether this field is executable.

Suggested tests

  • parseModelConfig resolves Anthropic apiKey from ANTHROPIC_API_KEY when raw apiKey is absent and the provider exists in the catalog.
  • The existing explicit ${ENV_VAR} behavior still works.
  • Missing raw apiKey and missing catalog/env value still fails with missing_api_key.
  • A provider without catalog apiKeyEnvVar keeps the current failure behavior.

Submitted with Codex.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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