code-yeongyu / code-yeongyu/senpi

[Bug]: stale ANTHROPIC_API_KEY captured by the multi-session RPC host silently overrides claude-sdk-oauth and surfaces only as "Credit balance is too low"

Open
#1,195 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
429
Forks
98
Avg merge
5h 3m
Merged PRs (30d)
526

Description

## Summary

A stale `ANTHROPIC_API_KEY` in the environment of the detached multi-session RPC host silently overrides the `claude-sdk-oauth` provider's subscription OAuth for **every session served by that host**. All affected sessions fail with a bare `Credit balance is too low` assistant message (usage all-zero), while nothing in the UI or logs indicates that API-key billing was used instead of the configured OAuth path. Because the host daemonizes and is reparented to systemd, the poisoned environment survives `unset`, new terminals, `claude /login`, and credential rotation — only killing the host recovers.

## Environment

- omo 5.0.0-0.beta.26 (engine: senpi 2026.8.28-2), bun global install
- `CLAUDE_AGENT_SDK_VERSION=0.3.241`, entrypoint `sdk-ts`
- Linux x64
- `settings.json`: `defaultProvider: claude-sdk-oauth`, `claudeSdkOauthProvider: { enabled: true, tokenInjection: "ambient" }`
- Ambient credentials: `~/.claude/.credentials.json`, `subscriptionType: "max"` (rate limit tier `default_claude_max_20x`) — no API-key billing intended

## What happened (timeline, KST)

1. `21:41` — a shell that happened to have `ANTHROPIC_API_KEY` exported (sourced from a project `.env`) triggered the spawn of the multi-session RPC host (`cli-main.js --mode rpc --multi-session --listen unix:///tmp/senpi-rpc-host-internal-*/host.sock`, daemonized via `modes/rpc/host-lifecycle.js`, PPID 1). The host captured that environment permanently.
2. `21:48–21:55` — sessions served by this host worked, but were silently billed against the API key's console org credits (25K–33K token calls visible in session JSONL).
3. `21:56` — the console org's credit balance ran out. **Four live sessions failed simultaneously**, and every new session failed instantly with `Credit balance is too low` (assistant message, `usage` all zero, `stopReason: "stop"`, diagnostics `claude_sdk_oauth_session_continuity { kind: "bootstrap", reason: "registry_miss" }`). 10+ session files affected.
4. `22:17–22:29` — `claude login`, credential refresh, and account rotation had no effect: the failing path never consults OAuth while the env var is present in the host.
5. Meanwhile a session hosted by a *different, older* senpi process without the env var kept working on the same machine with identical config/credentials — which is what made this so confusing to diagnose.

## Root cause

- The Claude Agent SDK child (`@anthropic-ai/claude-agent-sdk-linux-x64/claude --output-format stream-json`) inherits the RPC host's environment. With `ANTHROPIC_API_KEY` present, the SDK prefers it over ambient OAuth, so requests bill console API credits instead of the Max subscription.
- Verified empirically with two SDK children spawned minutes apart from different hosts:
- child of the poisoned RPC host: env contains `ANTHROPIC_API_KEY=sk-ant-api03-…` → `Credit balance is too low`
- child of a clean host: no key in env → OAuth, works
- Calling `POST /v1/messages` directly with that key returns HTTP 400 `"Your credit balance is too low to access the Anthropic API"` — the exact string surfaced in-session.
- The amplifier is the host architecture: the first client to spawn the RPC host donates its (possibly dirty) environment to **all** future sessions, indefinitely, because the daemon outlives every shell.

## Repro

```bash
export ANTHROPIC_API_KEY=
omo # first launch spawns the detached RPC host, which inherits the env
# -> every session through this host: "Credit balance is too low", despite valid Max OAuth
unset ANTHROPIC_API_KEY
omo # still broken; only `pkill -f 'mode rpc'` (host + host-lifecycle) recovers
```

## Suggested fixes

1. When `claude-sdk-oauth` is the selected provider with `tokenInjection: "ambient"` (i.e. OAuth is the user's intent), scrub `ANTHROPIC_API_KEY` from the SDK child's environment (or the RPC host's at startup) unless the user explicitly opted into API-key auth.
2. Failing that, surface the resolved credential path (api-key vs oauth) in the status line and/or attach it to provider errors, so `Credit balance is too low` on a subscription account is diagnosable without dumping `/proc//environ`.
3. Consider documenting (or fixing) that the multi-session RPC host pins the environment of whichever client first spawned it.

## Workaround

- `pkill -f 'modes/rpc/host-lifecycle'` + `pkill -f 'mode rpc'`, then relaunch from a clean shell.
- Defensive alias: `alias omo='env -u ANTHROPIC_API_KEY omo'`.

Contributor guide

Open the contributing guide

Research direction

Read modes/rpc/host-lifecycle.js and cli-main.js, then trace how the detached RPC host passes its environment to the Claude Agent SDK child and how provider selection is applied. Reproduce with and without ANTHROPIC_API_KEY, checking whether claude-sdk-oauth still uses OAuth and whether the resulting error identifies the credential path.

Written by the indexing model from the issue text.

Assessment

Tech stack
bun, typescript
Domain
authentication, backend, cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.