Problem
When --enable-api-proxy is active and GEMINI_API_KEY is set, the Gemini CLI receives a placeholder key (gemini-api-key-placeholder-for-credential-isolation) inside the agent container. The api-proxy sidecar is supposed to intercept requests on port 10003, strip the placeholder, and inject the real key before forwarding to generativelanguage.googleapis.com.
After the fix in #1944 (v0.68.3), routing works correctly — the proxy reports Gemini=true and listens on port 10003. However, requests to Google still fail with API_KEY_INVALID:
_ApiError: {"error":{"code":400,"message":"API key not valid. Please pass a valid API key.",
"status":"INVALID_ARGUMENT","reason":"API_KEY_INVALID"}}
The real GEMINI_API_KEY is confirmed valid (direct calls to generativelanguage.googleapis.com succeed).
Root Cause Analysis
The Gemini CLI uses @google/genai SDK, which sends the API key via the x-goog-api-key HTTP header. The api-proxy (containers/api-proxy/server.js) handles this:
- Line 1019-1021: Injects
{'x-goog-api-key': GEMINI_API_KEY} as injectHeaders
- Line 476-485 (
proxyRequest): Copies incoming headers, then Object.assign(headers, injectHeaders) to overwrite with real key
The STRIPPED_HEADERS set (line 44-51) includes authorization and x-api-key but not x-goog-api-key. Since Node.js lowercases incoming headers, the Object.assign should overwrite the placeholder.
Possible causes to investigate
-
The SDK sends the key as a query parameter (?key=...) rather than a header — The proxy does not strip or replace query-parameter keys, so the placeholder would be forwarded verbatim to Google. While the JS SDK uses the header approach, older SDK versions or the Gemini CLI itself may append key= to the URL.
-
Header duplication — If for any reason the header appears multiple times (e.g., headers.append() vs headers.set()), Google might read the first occurrence (the placeholder) and ignore the real key injected later by Object.assign.
-
Key encoding/truncation — The GEMINI_API_KEY env var might be truncated or encoding-mangled when passed through Docker environment variables to the api-proxy container.
Proposed Fix
-
Add x-goog-api-key to STRIPPED_HEADERS — This ensures the placeholder is always removed before the real key is injected, eliminating any header duplication risk.
-
Strip key= query parameter from the URL — In proxyRequest or in the Gemini-specific handler, detect and remove any key= query parameter from the forwarded URL to handle the query-parameter auth path:
// In the Gemini handler or proxyRequest for Gemini
const url = new URL(req.url, `http://${req.headers.host}`);
url.searchParams.delete('key');
req.url = url.pathname + url.search;
-
Add debug logging — Log the injected key length/preview for Gemini requests (the other providers already have this at line 488-500 but only for x-api-key and authorization, not x-goog-api-key).
Reproduction
- Set
GEMINI_API_KEY as a repository Actions secret
- Create a workflow with
engine: gemini
- Trigger the workflow — observe
API_KEY_INVALID error
Related
Problem
When
--enable-api-proxyis active andGEMINI_API_KEYis set, the Gemini CLI receives a placeholder key (gemini-api-key-placeholder-for-credential-isolation) inside the agent container. The api-proxy sidecar is supposed to intercept requests on port 10003, strip the placeholder, and inject the real key before forwarding togenerativelanguage.googleapis.com.After the fix in #1944 (v0.68.3), routing works correctly — the proxy reports
Gemini=trueand listens on port 10003. However, requests to Google still fail withAPI_KEY_INVALID:The real
GEMINI_API_KEYis confirmed valid (direct calls togenerativelanguage.googleapis.comsucceed).Root Cause Analysis
The Gemini CLI uses
@google/genaiSDK, which sends the API key via thex-goog-api-keyHTTP header. The api-proxy (containers/api-proxy/server.js) handles this:{'x-goog-api-key': GEMINI_API_KEY}asinjectHeadersproxyRequest): Copies incoming headers, thenObject.assign(headers, injectHeaders)to overwrite with real keyThe
STRIPPED_HEADERSset (line 44-51) includesauthorizationandx-api-keybut notx-goog-api-key. Since Node.js lowercases incoming headers, theObject.assignshould overwrite the placeholder.Possible causes to investigate
The SDK sends the key as a query parameter (
?key=...) rather than a header — The proxy does not strip or replace query-parameter keys, so the placeholder would be forwarded verbatim to Google. While the JS SDK uses the header approach, older SDK versions or the Gemini CLI itself may appendkey=to the URL.Header duplication — If for any reason the header appears multiple times (e.g.,
headers.append()vsheaders.set()), Google might read the first occurrence (the placeholder) and ignore the real key injected later byObject.assign.Key encoding/truncation — The
GEMINI_API_KEYenv var might be truncated or encoding-mangled when passed through Docker environment variables to the api-proxy container.Proposed Fix
Add
x-goog-api-keytoSTRIPPED_HEADERS— This ensures the placeholder is always removed before the real key is injected, eliminating any header duplication risk.Strip
key=query parameter from the URL — InproxyRequestor in the Gemini-specific handler, detect and remove anykey=query parameter from the forwarded URL to handle the query-parameter auth path:Add debug logging — Log the injected key length/preview for Gemini requests (the other providers already have this at line 488-500 but only for
x-api-keyandauthorization, notx-goog-api-key).Reproduction
GEMINI_API_KEYas a repository Actions secretengine: geminiAPI_KEY_INVALIDerrorRelated