问题 / The problem
abg 的入口是 #!/usr/bin/env bun,而 Bun 会自动把当前目录的 .env 加载进 process.env(文档,无需 import、无需配置、无法 opt out)。runClaude() 在 src/cli/claude.ts:114 用 env: process.env spawn 子进程 —— 这本身是完全正常的透传,但此时的 process.env 已经不是用户 shell 的环境了。
结果:项目 .env 里(本来就应该存在的)ANTHROPIC_API_KEY 进入 Claude Code,覆盖用户的 claude.ai 登录,静默切换到按量计费。Claude Code 确实会打印一行提示,但开头是 "claude.ai connectors are disabled",而不是"你现在在按 token 付费",并且立刻滚屏消失。
abg's entrypoint is #!/usr/bin/env bun, and Bun auto-loads ./.env from the working directory into process.env (docs) — no import, no configuration, no opt-out. runClaude() spawns the child with env: process.env (src/cli/claude.ts:114), an honest pass-through of an environment that is no longer the shell's.
Claude Code then finds ANTHROPIC_API_KEY set, prefers it over the claude.ai login, and bills every token to the metered API. It does print a warning, but it leads with "claude.ai connectors are disabled" rather than "you are now paying per token", on one line that immediately scrolls away.
Nobody wrote this bug. It emerges between a runtime convenience (Bun loading .env for your program) and a launcher pattern (passing process.env to a different program that reads credentials from the environment). Neither half is wrong on its own, and no code review of either component reveals it — the string ANTHROPIC_API_KEY does not appear anywhere in this repository, on any branch, in its entire history.
Notably, src/env-guard.ts already exists precisely because leaked/stale AGENTBRIDGE_* values in the environment cause confusing behaviour. This is the same class of problem, with money attached.
复现 / Reproduction
cd <any project with ANTHROPIC_API_KEY in .env>
env | grep -c ANTHROPIC # 0 — not in the shell
bun -e 'console.log(!!process.env.ANTHROPIC_API_KEY)' # true — but Bun loaded it
abg claude
# → Claude Code now bills to the API, not the subscription.
# → Confirm with /status inside the session, or: ps eww -p <pid> | grep ANTHROPIC
The variable never exists in the user's shell, so env | grep finds nothing, config files show nothing, and agent-bridge's own source shows nothing. It is effectively undiscoverable.
Found the expensive way: an unattended bridged pair ran for 2.5 hours on claude-opus-5, resending a ~400k-token conversation roughly every 20 seconds, entirely on API billing. ≈ $15 before it was noticed.
This is not an exotic setup — any LangChain / LangGraph / Anthropic SDK project keeps its key in .env, and that is exactly the kind of project people pair Claude and Codex on.
建议修复 / Suggested fix (warning only, no behaviour change)
最小的修复是只加警告、不改变任何行为 —— 默认剥离这个 key 会破坏那些刻意使用 API 计费的用户。
The minimal fix is to warn only and change nothing else — stripping the key by default would break users who deliberately run Claude Code on API billing. Concretely:
apiKeyBillingWarning(env) in src/cli/claude.ts — pure + exported, in the same style as mapChildExitCode ("Pure + exported so the mapping is unit-testable without spawning a process"), so the text is unit-testable.
- Called in
runClaude() on stderr with the [agentbridge] prefix, right after the max-permission notice and before spawn. Fail-open, never fatal, no prompt — matching every other guard in that function.
- Detects
ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN. Logs the variable name only, never the value.
- Explains the Bun
.env autoload, since that is the non-obvious part, and suggests env -u ANTHROPIC_API_KEY abg claude.
- Silenced with
AGENTBRIDGE_NO_API_KEY_WARNING=1.
- Testable in
src/unit-test/cli.test.ts without spawning a process, per the existing helpers there.
src/cli/resume.ts delegates to runClaude(), so no second call site is needed. src/cli/codex.ts is untouched — Codex does not read ANTHROPIC_API_KEY.
Sketch:
[agentbridge] ⚠️ ANTHROPIC_API_KEY is set — Claude Code will authenticate with it instead of your
[agentbridge] claude.ai subscription, and every token in this pair is billed to the API.
[agentbridge] abg runs on Bun, which auto-loads ./.env from the current directory, so this can be
[agentbridge] set even when your shell has it unset.
[agentbridge] Intended? Ignore this. Otherwise: env -u ANTHROPIC_API_KEY abg claude
[agentbridge] Silence with AGENTBRIDGE_NO_API_KEY_WARNING=1
后续可考虑 / Possible follow-ups (deliberately not in that PR)
- Strip
ANTHROPIC_API_KEY from the child env behind an opt-in flag.
- Disable Bun's
.env autoload in the launcher. Note: a bunfig.toml with env = false would not help installed users — bunfig resolves relative to the invocation directory, not the package. It would need --no-env-file in the shebang/launcher, or explicit env construction.
abg doctor already reports an env check via inspectAgentBridgeEnv — it could also report which auth source the pair will actually use.
上面的方案仅供参考,具体实现方式由维护者决定。
The sketch above is only a suggestion — the actual shape of the fix is the maintainers' call.
问题 / The problem
abg的入口是#!/usr/bin/env bun,而 Bun 会自动把当前目录的.env加载进process.env(文档,无需 import、无需配置、无法 opt out)。runClaude()在src/cli/claude.ts:114用env: process.envspawn 子进程 —— 这本身是完全正常的透传,但此时的process.env已经不是用户 shell 的环境了。结果:项目
.env里(本来就应该存在的)ANTHROPIC_API_KEY进入 Claude Code,覆盖用户的 claude.ai 登录,静默切换到按量计费。Claude Code 确实会打印一行提示,但开头是 "claude.ai connectors are disabled",而不是"你现在在按 token 付费",并且立刻滚屏消失。abg's entrypoint is#!/usr/bin/env bun, and Bun auto-loads./.envfrom the working directory intoprocess.env(docs) — no import, no configuration, no opt-out.runClaude()spawns the child withenv: process.env(src/cli/claude.ts:114), an honest pass-through of an environment that is no longer the shell's.Claude Code then finds
ANTHROPIC_API_KEYset, prefers it over the claude.ai login, and bills every token to the metered API. It does print a warning, but it leads with "claude.ai connectors are disabled" rather than "you are now paying per token", on one line that immediately scrolls away.Nobody wrote this bug. It emerges between a runtime convenience (Bun loading
.envfor your program) and a launcher pattern (passingprocess.envto a different program that reads credentials from the environment). Neither half is wrong on its own, and no code review of either component reveals it — the stringANTHROPIC_API_KEYdoes not appear anywhere in this repository, on any branch, in its entire history.Notably,
src/env-guard.tsalready exists precisely because leaked/staleAGENTBRIDGE_*values in the environment cause confusing behaviour. This is the same class of problem, with money attached.复现 / Reproduction
The variable never exists in the user's shell, so
env | grepfinds nothing, config files show nothing, and agent-bridge's own source shows nothing. It is effectively undiscoverable.Found the expensive way: an unattended bridged pair ran for 2.5 hours on
claude-opus-5, resending a ~400k-token conversation roughly every 20 seconds, entirely on API billing. ≈ $15 before it was noticed.This is not an exotic setup — any LangChain / LangGraph / Anthropic SDK project keeps its key in
.env, and that is exactly the kind of project people pair Claude and Codex on.建议修复 / Suggested fix (warning only, no behaviour change)
最小的修复是只加警告、不改变任何行为 —— 默认剥离这个 key 会破坏那些刻意使用 API 计费的用户。
The minimal fix is to warn only and change nothing else — stripping the key by default would break users who deliberately run Claude Code on API billing. Concretely:
apiKeyBillingWarning(env)insrc/cli/claude.ts— pure + exported, in the same style asmapChildExitCode("Pure + exported so the mapping is unit-testable without spawning a process"), so the text is unit-testable.runClaude()on stderr with the[agentbridge]prefix, right after the max-permission notice and before spawn. Fail-open, never fatal, no prompt — matching every other guard in that function.ANTHROPIC_API_KEYandANTHROPIC_AUTH_TOKEN. Logs the variable name only, never the value..envautoload, since that is the non-obvious part, and suggestsenv -u ANTHROPIC_API_KEY abg claude.AGENTBRIDGE_NO_API_KEY_WARNING=1.src/unit-test/cli.test.tswithout spawning a process, per the existing helpers there.src/cli/resume.tsdelegates torunClaude(), so no second call site is needed.src/cli/codex.tsis untouched — Codex does not readANTHROPIC_API_KEY.Sketch:
后续可考虑 / Possible follow-ups (deliberately not in that PR)
ANTHROPIC_API_KEYfrom the child env behind an opt-in flag..envautoload in the launcher. Note: abunfig.tomlwithenv = falsewould not help installed users — bunfig resolves relative to the invocation directory, not the package. It would need--no-env-filein the shebang/launcher, or explicit env construction.abg doctoralready reports anenvcheck viainspectAgentBridgeEnv— it could also report which auth source the pair will actually use.上面的方案仅供参考,具体实现方式由维护者决定。
The sketch above is only a suggestion — the actual shape of the fix is the maintainers' call.