mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-28 14:59:47 +00:00
fix(ai): drop empty-string env values before merge so they can't clobber config keys (#1249)
Claude Code injects ANTHROPIC_API_KEY='' to neuter subprocess LLM calls; an unconditional process.env spread let that empty string override a valid config.json key, breaking every gateway op with NO_ANTHROPIC_API_KEY. Filter '' / undefined before the merge; '0' and 'false' are preserved.
This commit is contained in:
@@ -61,6 +61,18 @@ export function buildGatewayConfig(c: GBrainConfig): AIGatewayConfig {
|
||||
chat_model: c.chat_model,
|
||||
chat_fallback_chain: c.chat_fallback_chain,
|
||||
base_urls: { ...envBaseUrls, ...(c.provider_base_urls ?? {}) }, // config wins over env
|
||||
env: { ...envFromConfig, ...process.env }, // process.env wins
|
||||
// #1249: process.env still wins over the config-plane fallback, BUT only for
|
||||
// keys that carry a real value. Claude Code (and some launchers) inject
|
||||
// ANTHROPIC_API_KEY='' to neuter subprocess LLM calls; an unconditional
|
||||
// `...process.env` lets that empty string clobber a valid config.json key, so
|
||||
// every gateway op then throws NO_ANTHROPIC_API_KEY. Drop empty-string /
|
||||
// undefined entries before the merge. Only '' and undefined are dropped —
|
||||
// '0' and 'false' are legitimate values and survive.
|
||||
env: {
|
||||
...envFromConfig,
|
||||
...Object.fromEntries(
|
||||
Object.entries(process.env).filter(([, v]) => v !== undefined && v !== ''),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,3 +85,35 @@ describe('buildGatewayConfig env-baseURL passthrough', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildGatewayConfig env empty-string clobber guard (#1249)', () => {
|
||||
test('an empty-string process.env value does NOT clobber a valid config-plane key', async () => {
|
||||
// Claude Code injects ANTHROPIC_API_KEY='' to neuter subprocess LLM calls.
|
||||
await withEnv({ ANTHROPIC_API_KEY: '' }, async () => {
|
||||
const cfg = buildGatewayConfig({
|
||||
anthropic_api_key: 'sk-config-plane',
|
||||
} as unknown as GBrainConfig);
|
||||
expect(cfg.env.ANTHROPIC_API_KEY).toBe('sk-config-plane');
|
||||
});
|
||||
});
|
||||
|
||||
test('a real process.env value still wins over the config-plane fallback', async () => {
|
||||
await withEnv({ ANTHROPIC_API_KEY: 'sk-env-plane' }, async () => {
|
||||
const cfg = buildGatewayConfig({
|
||||
anthropic_api_key: 'sk-config-plane',
|
||||
} as unknown as GBrainConfig);
|
||||
expect(cfg.env.ANTHROPIC_API_KEY).toBe('sk-env-plane');
|
||||
});
|
||||
});
|
||||
|
||||
test("legitimate falsy-but-present values ('0' / 'false') are preserved, not dropped", async () => {
|
||||
await withEnv(
|
||||
{ GBRAIN_TEST_ZERO_VAL: '0', GBRAIN_TEST_FALSE_VAL: 'false' },
|
||||
async () => {
|
||||
const cfg = buildGatewayConfig(baseConfig);
|
||||
expect(cfg.env.GBRAIN_TEST_ZERO_VAL).toBe('0');
|
||||
expect(cfg.env.GBRAIN_TEST_FALSE_VAL).toBe('false');
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user