Skip to content

fix: propagate openai_api_key and anthropic_api_key from config to SDK env#141

Open
Hybirdss wants to merge 1 commit intogarrytan:masterfrom
Hybirdss:fix/anthropic-api-key-config-propagation
Open

fix: propagate openai_api_key and anthropic_api_key from config to SDK env#141
Hybirdss wants to merge 1 commit intogarrytan:masterfrom
Hybirdss:fix/anthropic-api-key-config-propagation

Conversation

@Hybirdss
Copy link
Copy Markdown

@Hybirdss Hybirdss commented Apr 15, 2026

Summary

Both openai_api_key and anthropic_api_key in ~/.gbrain/config.json
were dead fields at runtime. The SDK clients are instantiated without an
explicit apiKey at src/core/embedding.ts:24 (new OpenAI()) and
src/core/search/expansion.ts:19 (new Anthropic()), so both fall back
to the SDK's ambient process.env.*_API_KEY lookup. Writing either key
to the config file produced silent failure.

$ rg 'openai_api_key|anthropic_api_key' src/ --type=ts | grep -v test
src/core/config.ts:14:  openai_api_key?: string;
src/core/config.ts:15:  anthropic_api_key?: string;
src/commands/init.ts:73:    ...(opts.apiKey ? { openai_api_key: opts.apiKey } : {}),
src/commands/init.ts:145:    ...(opts.apiKey ? { openai_api_key: opts.apiKey } : {}),

Only the interface declarations and two init.ts writes (which set the
field from a CLI flag) — no runtime reader for either key.

Fix (src/core/config.ts):

  1. Mirror ANTHROPIC_API_KEY env var into the merged config, matching
    the existing OPENAI_API_KEY merge line (parity at the config-object
    layer).
  2. For each key, if the merged config has a value and the corresponding
    env var is unset, propagate the config value into process.env so
    the SDK's ambient lookup picks it up on next client construction.
    Env var still wins when both are set.

No refactor to embedding.ts or expansion.ts — the singletons'
instantiation is unchanged and the SDK contract is preserved.

Test Coverage

CODE PATH COVERAGE
==================
[+] loadConfig() — API key propagation (parameterized over both keys)
    ├── [★★★ TESTED] config.json value is merged into loaded config — openai_api_key
    ├── [★★★ TESTED] config.json value is merged into loaded config — anthropic_api_key
    ├── [★★★ TESTED] config.json value propagates to process.env for SDK ambient lookup — OPENAI_API_KEY
    ├── [★★★ TESTED] config.json value propagates to process.env for SDK ambient lookup — ANTHROPIC_API_KEY
    ├── [★★★ TESTED] env var is merged into loaded config — OPENAI_API_KEY
    ├── [★★★ TESTED] env var is merged into loaded config — ANTHROPIC_API_KEY
    ├── [★★★ TESTED] env var takes precedence over config file entry — openai_api_key
    ├── [★★★ TESTED] env var takes precedence over config file entry — anthropic_api_key
    ├── [★★★ TESTED] env var is not overwritten by config propagation when both set — OPENAI_API_KEY
    ├── [★★★ TESTED] env var is not overwritten by config propagation when both set — ANTHROPIC_API_KEY
    ├── [★★★ TESTED] no openai_api_key set anywhere leaves env clean
    └── [★★★ TESTED] no anthropic_api_key set anywhere leaves env clean

Tests: 846 → 858 (+12 new, parameterized over both keys, +0 test files).
984 total runs, 0 failures.

Subprocess-per-test pattern (same as test/upgrade.test.ts) because
os.homedir() caches HOME at process start on Bun — in-process HOME
overrides do not affect homedir() resolution, so each case spawns a
fresh bun -e with a scoped HOME env.

Pre-Landing Review

No issues found. Both Anthropic and OpenAI SDK constructors resolve
their API key from process.env at first request time, so propagating
into the environment before the singleton is constructed (loadConfig()
runs at src/mcp/server.ts:65 before every op.handler call) is
sufficient and non-invasive.

Eval Results

No prompt-related files changed — evals skipped.

TODOS

No TODO items completed in this PR.

Test plan

  • bun test test/config.test.ts — 20 pass, 0 fail (12 new)
  • bun test — 858 pass, 126 skip, 0 fail (984 total)
  • bun run build — compiled bin/gbrain

Documentation

  • CHANGELOG.md: added 0.10.2 — 2026-04-16 entry under "Fixed"
    covering both keys
  • VERSION and package.json: bumped to 0.10.2
  • README / CLAUDE.md / engine docs: no updates needed — the fix
    restores the behavior the existing config-file shape already implied.

🤖 Generated with Claude Code

…K env

Both fields were declared on GBrainConfig but neither was consumed at
runtime — new OpenAI() (embedding.ts:24) and new Anthropic()
(search/expansion.ts:19) both instantiate without an explicit apiKey,
so they rely on the SDK ambient *_API_KEY env var lookup. Writing
either key to ~/.gbrain/config.json had no effect.

loadConfig() now:
- mirrors ANTHROPIC_API_KEY env var into the merged config (parity
  with the existing OPENAI_API_KEY merge line)
- propagates merged.openai_api_key / merged.anthropic_api_key into
  process.env when the env var is unset, so the SDKs' ambient lookup
  picks it up at new OpenAI() / new Anthropic() construction

12 new subprocess-isolated tests (parameterized over both keys) in
test/config.test.ts cover all precedence cases. Subprocess pattern
matches test/upgrade.test.ts — os.homedir() caches HOME at process
start on Bun.

Tests: 846 → 858 pass, 0 regressions. Version bumped to 0.10.2.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@Hybirdss Hybirdss force-pushed the fix/anthropic-api-key-config-propagation branch from beb50ed to 31d1c1a Compare April 15, 2026 21:55
@Hybirdss Hybirdss changed the title fix: propagate anthropic_api_key from config to Anthropic SDK fix: propagate openai_api_key and anthropic_api_key from config to SDK env Apr 15, 2026
@Hybirdss
Copy link
Copy Markdown
Author

Scope amended shortly after submission: originally anthropic-only claiming parity with openai_api_key, but on self-review I noticed openai_api_key has the same dead-field pattern at the SDK layer (new OpenAI() in embedding.ts:24 also passes no explicit apiKey). Extended the fix to both keys symmetrically with parameterized tests.

Note: gbrain init --api-key <key> still writes openai_api_key only (init.ts:73,145). Extending init to accept an Anthropic key is a separate change — kept out of this PR to avoid scope creep.

Happy to split into two PRs (env-merge consistency vs. config→env propagation) if you prefer a smaller change, or to narrow back to anthropic-only if this is already in a queued wave commit. Just let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant