Summary
Gemini CLI agents spawned by CCB cannot honor custom endpoints (proxy providers such as OneChats) because two independent env allowlists drop the env variable that Gemini CLI specifically reads for endpoint override: GOOGLE_GEMINI_BASE_URL.
End-to-end symptom: Gemini agent tries to call https://generativelanguage.googleapis.com (Google's production API) with a proxy-issued `sk-...` key → API_KEY_INVALID.
Evidence
Tested on ${CCB_VERSION} installed at ~/.local/share/codex-dual/. Host shell exports:
```bash
export GOOGLE_GEMINI_BASE_URL="https://chatapi.onechats.ai"
export GEMINI_MODEL="gemini-3.1-pro-preview"
export GEMINI_API_KEY="sk-..."
```
Outside CCB (direct `gemini` CLI invocation) — requests succeed, traffic goes to the proxy.
Inside CCB agent pane — request fails with:
```json
{"error": {"code": 400, "message": "API key not valid...",
"details": [{"metadata": {"service": "generativelanguage.googleapis.com"}}]}}
```
The `service` field confirms the CLI defaulted to Google's production endpoint, meaning `GOOGLE_GEMINI_BASE_URL` was never in its env.
```
$ cat /proc/$PID_OF_GEMINI_NODE/environ | tr '\0' '\n' | grep -iE '^(GEMINI|GOOGLE)'
GEMINI_API_KEY=sk-...
GEMINI_ROOT=...
GOOGLE_GEMINI_BASE_URL is missing
GEMINI_MODEL is missing
```
Root cause
Two hardcoded allowlists filter the env on its way from the caller shell → ccbd → tmux server → agent pane → Gemini CLI process:
-
`lib/runtime_env/control_plane.py` — `_CONTROL_PLANE_ALLOWLIST`:
- Includes: `GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_API_BASE`, `GOOGLE_GENAI_USE_VERTEXAI`
- Missing: `GOOGLE_GEMINI_BASE_URL`, `GEMINI_MODEL`
- Additionally `CONTROL_PLANE_BLOCKED_PREFIXES` includes `'GEMINI'`, explicitly blocking any non-allowlisted `GEMINI_*` var (which kills `GEMINI_MODEL`).
-
`lib/provider_profiles/materializer.py` — `_API_ENV_KEYS['gemini']`:
- Same vocabulary, same gap — filters again at provider-profile materialization.
Claude provider handles this gracefully because `ANTHROPIC_BASE_URL` IS allowlisted. The gemini vocabulary uses an older Google Generative AI SDK naming convention (`GOOGLE_API_BASE`) that the current Gemini CLI does not honor as an endpoint override — empirical test with renaming `GOOGLE_GEMINI_BASE_URL` → `GOOGLE_API_BASE` reproduces the same "API_KEY_INVALID" failure against the production endpoint, proving the CLI ignores `GOOGLE_API_BASE`.
Fix
Minimal two-line patch to each file (tested locally, Gemini pane traffic now correctly routes to the custom endpoint):
```diff
lib/provider_profiles/materializer.py
- 'gemini': {'GEMINI_API_KEY', 'GOOGLE_API_KEY', 'GOOGLE_API_BASE', 'GOOGLE_GENAI_USE_VERTEXAI'},
- 'gemini': {'GEMINI_API_KEY', 'GOOGLE_API_KEY', 'GOOGLE_API_BASE', 'GOOGLE_GENAI_USE_VERTEXAI',
-
'GOOGLE_GEMINI_BASE_URL', 'GEMINI_MODEL'},
lib/runtime_env/control_plane.py _CONTROL_PLANE_ALLOWLIST
- 'GEMINI_MODEL',
'GOOGLE_API_BASE',
'GOOGLE_API_KEY',
- 'GOOGLE_GEMINI_BASE_URL',
'GOOGLE_GENAI_USE_VERTEXAI',
```
Longer-term direction (discussion, not part of the fix)
The allowlists are hardcoded in source. When a user needs a new var (custom proxy, vendor-specific knob), they have to fork or locally patch CCB. A config-driven extension point would be preferable — e.g. `ccb.config` could declare `provider_profile.env_allowlist: [NEW_VAR_1, NEW_VAR_2]` which gets merged into the base set at materialisation time. Out of scope for this issue but worth tracking as a separate enhancement.
Affected versions
Reproduced on CCB as installed via the standard codex-dual layout at `~/.local/share/codex-dual/` on Linux (`Linux 5.15.0-176-generic`).
🤖 Filed via Claude Code after empirical reproduction + local patch verification.
Summary
Gemini CLI agents spawned by CCB cannot honor custom endpoints (proxy providers such as OneChats) because two independent env allowlists drop the env variable that Gemini CLI specifically reads for endpoint override:
GOOGLE_GEMINI_BASE_URL.End-to-end symptom: Gemini agent tries to call
https://generativelanguage.googleapis.com(Google's production API) with a proxy-issued `sk-...` key →API_KEY_INVALID.Evidence
Tested on
${CCB_VERSION}installed at~/.local/share/codex-dual/. Host shell exports:```bash
export GOOGLE_GEMINI_BASE_URL="https://chatapi.onechats.ai"
export GEMINI_MODEL="gemini-3.1-pro-preview"
export GEMINI_API_KEY="sk-..."
```
Outside CCB (direct `gemini` CLI invocation) — requests succeed, traffic goes to the proxy.
Inside CCB agent pane — request fails with:
```json
{"error": {"code": 400, "message": "API key not valid...",
"details": [{"metadata": {"service": "generativelanguage.googleapis.com"}}]}}
```
The `service` field confirms the CLI defaulted to Google's production endpoint, meaning `GOOGLE_GEMINI_BASE_URL` was never in its env.
```
$ cat /proc/$PID_OF_GEMINI_NODE/environ | tr '\0' '\n' | grep -iE '^(GEMINI|GOOGLE)'
GEMINI_API_KEY=sk-...
GEMINI_ROOT=...
GOOGLE_GEMINI_BASE_URL is missing
GEMINI_MODEL is missing
```
Root cause
Two hardcoded allowlists filter the env on its way from the caller shell → ccbd → tmux server → agent pane → Gemini CLI process:
`lib/runtime_env/control_plane.py` — `_CONTROL_PLANE_ALLOWLIST`:
`lib/provider_profiles/materializer.py` — `_API_ENV_KEYS['gemini']`:
Claude provider handles this gracefully because `ANTHROPIC_BASE_URL` IS allowlisted. The gemini vocabulary uses an older Google Generative AI SDK naming convention (`GOOGLE_API_BASE`) that the current Gemini CLI does not honor as an endpoint override — empirical test with renaming `GOOGLE_GEMINI_BASE_URL` → `GOOGLE_API_BASE` reproduces the same "API_KEY_INVALID" failure against the production endpoint, proving the CLI ignores `GOOGLE_API_BASE`.
Fix
Minimal two-line patch to each file (tested locally, Gemini pane traffic now correctly routes to the custom endpoint):
```diff
lib/provider_profiles/materializer.py
lib/runtime_env/control_plane.py _CONTROL_PLANE_ALLOWLIST
'GOOGLE_API_BASE',
'GOOGLE_API_KEY',
'GOOGLE_GENAI_USE_VERTEXAI',
```
Longer-term direction (discussion, not part of the fix)
The allowlists are hardcoded in source. When a user needs a new var (custom proxy, vendor-specific knob), they have to fork or locally patch CCB. A config-driven extension point would be preferable — e.g. `ccb.config` could declare `provider_profile.env_allowlist: [NEW_VAR_1, NEW_VAR_2]` which gets merged into the base set at materialisation time. Out of scope for this issue but worth tracking as a separate enhancement.
Affected versions
Reproduced on CCB as installed via the standard codex-dual layout at `~/.local/share/codex-dual/` on Linux (`Linux 5.15.0-176-generic`).
🤖 Filed via Claude Code after empirical reproduction + local patch verification.