ANTHROPIC_BASE_URL 设了却不生效
现象
给 Claude Code 挂第三方中转站(one-api / new-api 那类 API 网关),配置写得规规矩矩,~/.zshrc 里三行齐全:
export ANTHROPIC_BASE_URL="https://xxxx.example.com"
export ANTHROPIC_AUTH_TOKEN="sk-..."
export CLAUDE_CODE_USE_VERTEX=0
新开终端,claude 启动,顶部写着:
Fable 5 · Google Vertex AI
Vertex。中转站的 base URL 像是根本没被看见。
同一天还有第二件事:CloudCLI(claudecodeui 改名后的 Web UI,用来远程控制本机 claude)由 macOS launchd 拉起,登进去一直让选 provider,提示未认证。可命令行里 claude 明明已经能走中转站了——同一个用户,同一台机器。
两个现象,两个独立的坑,共同点是:环境变量你以为设上了,进程里其实不是那么回事。
坑一:settings.json 的 env 比 shell export 大
先查环境变量本身有没有生效。echo $ANTHROPIC_BASE_URL 是对的,echo $CLAUDE_CODE_USE_VERTEX 也是 0。shell 这层没问题。
那就翻 Claude Code 自己的配置。~/.claude/settings.json 的 env 块里躺着三行测试残留:
{
"env": {
"CLAUDE_CODE_USE_VERTEX": "1",
"ANTHROPIC_VERTEX_PROJECT_ID": "hello",
"CLOUD_ML_REGION": "global"
}
}
ANTHROPIC_VERTEX_PROJECT_ID 值是 hello——一眼就知道是当初试 Vertex 时随手填的,试完忘了删。
根因清楚了:settings.json 的 env 字段是「强制注入」,优先级高于 shell 里的 export。 你在 zshrc 里写的 CLAUDE_CODE_USE_VERTEX=0,进程启动时被这里的 "1" 盖掉。Claude Code 检测到 Vertex 配置齐活,就走 Vertex 通道去了,ANTHROPIC_BASE_URL 从头到尾没人问津——它压根不在那条代码路径上。
修复就一件事:把这三行删掉。
claude -p "1+1"
# 正常出结果,走的是中转站
这里值得单独强调一句:env 的优先级,和 model 字段的优先级链(--model > ANTHROPIC_MODEL > settings.json > 默认,实测过)是两回事。model 那条链上 settings.json 排在环境变量后面,是「你没设我才用」;env 这个子字段反过来,是「我设了就盖你」。它还藏在 JSON 文件里,不像 shell 配置那样天天能看见——这才是它比 model 优先级更容易吃亏的地方。
坑二:launchd 启动的服务不读你的 shell 配置
命令行修好了,CloudCLI 那边还是未认证。
打开它的 plist:
<!-- ~/Library/LaunchAgents/com.xxxx.claudecodeui.plist -->
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin</string>
<key>HOME</key>
<string>/Users/xxxx</string>
</dict>
只有 PATH 和 HOME。没有 ANTHROPIC_BASE_URL,没有 ANTHROPIC_AUTH_TOKEN。
根因:launchd 不读 ~/.zshrc。 它跟 Linux 上的 systemd 是一个道理——服务进程不是从交互 shell 里 fork 出来的,你在 .zshrc / .bashrc 里写的任何 export,对它统统不可见。CloudCLI 自己环境是空的,透传给 claude 子进程的自然也是空的。
修复不是往 plist 里塞环境变量(那等于把 token 写进一个 XML,还要多维护一处),而是把中转站配置落到 ~/.claude/settings.json 的 env 块:
{
"env": {
"ANTHROPIC_BASE_URL": "https://xxxx.example.com",
"ANTHROPIC_AUTH_TOKEN": "sk-..."
}
}
写在这儿,两条路都能读到:
- Claude Code CLI 每次启动都读 settings.json 的 env,不依赖父进程给了什么;
- CloudCLI 的认证逻辑专门读这个文件——它
loadSettingsEnv的注释写得明明白白,「即使服务进程 env 为空也能用」。
同一个坑,坑一里 env 是加害者,坑二里它是解药。
验证:用 env -i 模拟服务环境
改完别急着重启 launchd 再登一次 Web UI 试——那一轮反馈太长。直接在终端里把环境清空,模拟服务进程的裸环境:
env -i HOME="$HOME" PATH="/usr/local/bin:/usr/bin:/bin" claude -p "1+1"
env -i 丢掉所有继承来的环境变量,只留你显式给的那两个——launchd 给什么,这里就给什么。这条命令能出结果,说明配置确实落在了「工具自己读得到」的地方,而不是靠 shell 喂进去的。
这个手法对付一切「命令行能跑、服务里跑不了」都好使:cron、systemd、Docker entrypoint,都是同一类环境断裂。
带走的教训
- 排查「环境变量设了却不生效」,第一站是
~/.claude/settings.json的env。 有同名键就是它赢,shell 里 export 多少遍都没用。这跟model的优先级链方向相反,别拿一套记忆套两处。 - 服务管理器不读 shell 配置。 launchd、systemd、cron 启的进程都看不见你的
.zshrc。依赖环境变量的工具一旦交给它们启动,配置就得落到「工具自己读得到的地方」——settings.json、服务的EnvironmentVariables、独立 env 文件,三选一。 env -i是模拟干净服务环境的最快验证手段。 不用重启服务、不用重登,一条命令就知道配置落点对不对。- 测试残留必须清。
ANTHROPIC_VERTEX_PROJECT_ID: "hello"这种一看就是临时填的值,试完当场删。它不会报错,只会在几个月后跟你的新配置打架,而你完全想不到往那儿看。