Gateway model discovery silently fails when auth comes from apiKeyHelper

Open 💬 3 comments Opened May 6, 2026 by ericwinsor-aisi

Summary

When ANTHROPIC_BASE_URL points at a custom gateway and authentication is provided only via apiKeyHelper (no ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY env var), gateway /v1/models discovery never runs and gateway models never appear in the /model picker.

Environment

  • Claude Code 2.1.131, native installer (linux-x64 Bun-compiled binary at ~/.local/share/claude/versions/2.1.131)
  • Build: GIT_SHA c122cd1bd5247f2a4bde8bcaec712fb1dff247e8, BUILD_TIME 2026-05-06T06:09:35Z
  • Also reproduces on 2.1.129 (first version with the CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY opt-in)

Reproduction

// /etc/claude-code/managed-settings.json (or user settings)
{
  "apiKeyHelper": "/usr/local/bin/some-key-helper.sh",
  "env": { "ANTHROPIC_API_KEY": "" }
}
// ~/.claude/settings.json
{
  "env": {
    "ANTHROPIC_BASE_URL": "http://127.0.0.1:5757",
    "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1"
  }
}
  1. Launch claude
  2. ~/.claude/cache/gateway-models.json is never written; /model shows no gateway entries
  3. ~/.claude/debug/latest contains no [gatewayDiscovery] lines, and shows [Bootstrap] Skipped: no usable OAuth or API key (same race)

Root cause

Identifiers below are minified names from the 2.1.131 linux-x64 bundle (/$bunfs/root/src/entrypoints/cli.js, SHA above). Source can be located by grepping for the string literals "[gatewayDiscovery]" and "gateway-models.json".

The discovery function (minified Dr7, contains all [gatewayDiscovery] log strings) is called fire-and-forget during startup init and guards on having a token or key before fetching:

let $ = process.env.ANTHROPIC_AUTH_TOKEN, q = sX();
if (!$ && !q) return;          // ← bails here, no log line

sX() resolves the API key; for the apiKeyHelper branch it returns xr8(), which is a synchronous cache read:

function xr8() { return Hx?.value ?? null }

Hx is only populated when the async helper shell-out (FCH(), execFile of the configured script) resolves. Nothing on the init path awaits it before discovery runs, so Hx is still null, q is null, and discovery returns silently before ever calling fetch(${baseUrl}/v1/models?limit=1000).

Suggested fix

Either await the helper in the discovery function (FCH() already returns the key):

let q = sX() ?? await FCH();

…or drop the auth guard for this fetch — many gateways don't require auth on GET /v1/models, and the header construction already handles the no-key case (...$?{Authorization:...}:q?{"x-api-key":...}:{}).

Workaround

Pre-write ~/.claude/cache/gateway-models.json (the picker reader only checks the env-flag gate and baseUrl match, not whether the fetch succeeded), or set a placeholder ANTHROPIC_AUTH_TOKEN and tolerate the "Auth conflict" startup warning.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗