DEV Community

Vinh Nguyen
Vinh Nguyen

Posted on

Exporting ANTHROPIC_BASE_URL does not reach the Claude Code panel in VSCode

export ANTHROPIC_BASE_URL=http://127.0.0.1:20128 in your shell routes the claude CLI to a local endpoint. Open the Claude Code panel in VSCode in the same project and it still talks to Anthropic directly. The two live on different environment surfaces, and that is the whole problem.

Why the shell variable does not carry over

The CLI reads the environment of the process you started it from. The VSCode panel does not: the extension spawns its agent process from the VSCode window process, so its environment is whatever VSCode itself was started with — the desktop/login environment, not the shell you typed export into five seconds ago.

Two consequences that bite in opposite directions:

  • Export in a terminal, then open the panel: nothing happens. The panel never saw the variable.
  • Launch VSCode from that terminal instead, so it inherits the vars: now every workspace in that window is routed, including the ones where you wanted the default Anthropic connection.

The escape hatch the extension gives you is a settings key, claudeCode.environmentVariables, which is a list of {name, value} pairs injected into the agent process. You can check what your panel actually sees right now — this runs anywhere jq does:

# macOS
S="$HOME/Library/Application Support/Code/User/settings.json"
# Linux:   S="$HOME/.config/Code/User/settings.json"
# Windows: S="$APPDATA/Code/User/settings.json"

jq -r '.["claudeCode.environmentVariables"] // []
  | if length == 0
    then "panel env: (none set) -> extension inherits the VSCode process env"
    else (.[] | "\(.name)=\(if .name|test("TOKEN|KEY") then "***" else .value end)")
    end' "$S"
Enter fullscreen mode Exit fullscreen mode

On a machine that has never been switched, that prints the (none set) line. That is the honest answer to "why is my panel ignoring the variable".

Editing that key by hand is where it goes wrong

claudeCode.environmentVariables is a plain array in your user settings.json, and hand-editing it has three failure modes I hit before writing anything:

  1. It has machine scope. It is not per-workspace. Turn routing on for one project and every VSCode window on that machine is routed.
  2. Turning it off means deleting entries, not flipping a flag. Leave ANTHROPIC_BASE_URL behind while removing the token and the panel points at a router with no credential.
  3. It shares the array with unrelated variables. Anything else you put there — proxy settings, a NODE_EXTRA_CA_CERTS — is one careless overwrite away from gone.

So the toolkit I ended up with treats the array as partly owned. It manages exactly six names:

ANTHROPIC_BASE_URL
ANTHROPIC_AUTH_TOKEN
ANTHROPIC_API_KEY
ANTHROPIC_MODEL
ANTHROPIC_SMALL_FAST_MODEL
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC
Enter fullscreen mode Exit fullscreen mode

Entries whose name is not on that list are read, kept aside, and written back untouched. Before any write it copies settings.json to settings.json.bak-claude-router, and if serialisation throws it restores the backup and exits non-zero rather than leaving you with half a settings file.

& "$env:USERPROFILE\.claude\9router\vscode-switch.ps1" on      # inject the six
& "$env:USERPROFILE\.claude\9router\vscode-switch.ps1" status  # print base URL + model, never the token
& "$env:USERPROFILE\.claude\9router\vscode-switch.ps1" off     # remove only the six
Enter fullscreen mode Exit fullscreen mode

on and off need a Developer: Reload Window afterwards — the agent process reads its environment once at spawn.

The terminal side keeps the opposite property

For the CLI the right scope is the narrow one, so there routing is per-process and nothing is persisted:

claude                    # default Anthropic connection
claude-9router            # same CLI, routed
claude-9router --resume   # normal Claude CLI arguments pass through
Enter fullscreen mode Exit fullscreen mode

claude-9router sets the variables inside its own process, unsets ANTHROPIC_API_KEY so a leftover key cannot win over the router token, then execs claude. Close the window and the routing is gone. Nothing in ~/.claude is rewritten.

That asymmetry is deliberate and it is the actual design decision: the terminal gets process scope because a shell is cheap to start, the panel gets machine scope with an explicit on/off because the extension gives no narrower hook. Cost of the second: you cannot have one routed window and one direct window at the same time. If you need both, use the panel for one and claude-9router in a terminal for the other.

Configuration and what it does not do

The endpoint is a 9Router instance, whose Anthropic-compatible /v1/messages fronts a long list of providers. The toolkit hard-codes none of them; it reads a gitignored local file:

{
  "baseUrl": "http://127.0.0.1:20128",
  "authToken": "your-9router-api-key",
  "mainModel": "provider/model-id",
  "smallFastModel": "provider/smaller-model-id"
}
Enter fullscreen mode Exit fullscreen mode

Limits, stated plainly:

  • Windows only. PowerShell 5.1+, and the VSCode settings path is the Windows one. macOS and Linux are not supported today.
  • Routing the client does not make the model Claude. The Claude Code UI is a shell; the answers come from whatever provider you selected in 9Router. Tool use, streaming, vision and prompt caching vary by provider — test the exact model before trusting it.
  • Provider terms are yours to check. Some subscriptions and OAuth sessions are not licensed for proxy or router use, and accounts do get restricted for it. Route only accounts you are authorised to route.
  • No benchmark numbers here because I have not measured any. The repo does not claim latency or cost figures either.

Repo, and where a second pair of hands helps

Code, tests and setup guide: https://github.com/vinhnguyenthanhdn/claude-router (MIT).

The open gap is the platform one: the Windows paths are the only ones implemented and tested, and porting is work I have not done. Three issues are scoped and open:

If you only want to confirm the diagnosis rather than write code: run the jq snippet above on your machine and tell me what your panel is actually inheriting. That answer is useful on its own.

Top comments (1)

Collapse
 
reidmarlow profile image
Reid Marlow

VS Code makes this easy to misread because the terminal and the extension are two different process trees. I usually set a harmless test var first, confirm the panel can see it, and only then point Claude Code at a local endpoint.