Summary
We observed a surprising auth-state mutation in Gemini CLI during an ACP / non-interactive flow.
In an isolated HOME with durable Gemini auth pinned to Google sign-in (oauth-personal), a risky ACP path with ambient GEMINI_API_KEY was able to drift the saved auth selection away from OAuth and into the API-key lane.
The concerning part is not only the runtime fallback. The ACP/non-interactive flow appears able to persist the fallback auth method back into durable local settings for later runs.
I am reporting this as observed behavior that may be unintended. If this is actually expected for ACP/non-interactive flows, then I think there should at least be a way to opt out of durable auth mutation.
Version / environment
- Observed on Gemini CLI
v0.38.2
v0.38.2 is also the current latest GitHub release at the time of filing
- Surface: ACP / non-interactive execution
- Prior durable auth state in the isolated HOME:
~/.gemini/settings.json
security.auth.selectedType = oauth-personal
security.auth.enforcedType = oauth-personal
- valid cached Google account / OAuth credential files present
What we observed
We ran two isolated-HOME stage cases without touching the live ~/.gemini directory.
Case A, hardened path
- durable Gemini settings pinned to
oauth-personal
- an outer launcher kept explicit selection pressure toward the OAuth lane
GEMINI_API_KEY was still intentionally injected into the staged process
Result:
- the staged call did not complete successfully, but the durable auth state remained pinned to
oauth-personal
- no auth drift happened
Case B, risky path
- removed the outer selection guard
- launched Gemini through a bare ACP path with ambient
GEMINI_API_KEY
Result:
- after the staged run, the isolated
~/.gemini/settings.json had drifted to selectedType = gemini-api-key
- the staged OAuth/account lane was no longer the active durable lane for subsequent runs
This makes the problem look like credential precedence plus durable state mutation, not just an expired cache.
Minimal reproduction shape
I do not yet have a pure-gemini-cli-only repro without an ACP client, but the minimal observed shape was:
- Create an isolated
HOME
- Stage
~/.gemini/settings.json with:
selectedType = oauth-personal
enforcedType = oauth-personal
- Stage valid cached OAuth/account files in that isolated HOME
- Export
GEMINI_API_KEY into the environment
- Start Gemini CLI through an ACP/non-interactive path that does not independently pin the OAuth lane
- Execute one non-interactive prompt turn
- Inspect the isolated
~/.gemini/settings.json
Observed result in the risky path:
security.auth.selectedType changed from oauth-personal to gemini-api-key
The exact helper I used on my side was an external ACP client. If helpful, I can provide a smaller reproduction packet for that path.
Why this seems surprising
From the docs, security.auth.selectedType reads as the durable selected auth method, and security.auth.enforcedType reads as the required auth method:
docs/reference/configuration.md
I did not find an obvious note in the README/configuration docs saying that ACP/non-interactive fallback auth is expected to rewrite the durable auth selection based on ambient env credentials.
Source seams that look relevant
I realize bundled code can differ from source, so below are the corresponding source-level seams from main that appear relevant.
1. ACP authenticate persists the chosen method into durable settings
packages/cli/src/acp/acpClient.ts
async authenticate(req: acp.AuthenticateRequest): Promise<void> {
const { methodId } = req;
const method = z.nativeEnum(AuthType).parse(methodId);
...
await this.context.config.refreshAuth(method, apiKey, baseUrl, headers);
this.settings.setValue(
SettingScope.User,
'security.auth.selectedType',
method,
);
}
2. Non-interactive auth validation allows env discovery into the effective auth type
packages/cli/src/validateNonInterActiveAuth.ts
const effectiveAuthType = configuredAuthType || getAuthTypeFromEnv();
Those two together look risky in ACP/non-interactive flows:
- env makes the API-key lane eligible
- ACP authenticates with that method
- durable
selectedType gets rewritten
Expected behavior
One of these would feel safer:
- ACP/non-interactive fallback auth should not rewrite durable
security.auth.selectedType, or
- only explicit user-driven auth-selection flows should persist
selectedType, or
- ACP/non-interactive flows should require an explicit opt-in before changing durable auth preference, or
- if this behavior is intentional, there should be a documented auth-lock / do-not-persist mechanism for mixed OAuth + API-key environments
Questions
- Is this durable auth rewrite in ACP/non-interactive flows intentional?
- If yes, would you accept a feature request for an auth-lock / no-persist mode?
- If no, does the
acpClient.ts persistence path look like the right fix seam?
Thanks. I can provide more exact before/after state and the ACP-side repro details if that would help narrow this further.
Summary
We observed a surprising auth-state mutation in Gemini CLI during an ACP / non-interactive flow.
In an isolated HOME with durable Gemini auth pinned to Google sign-in (
oauth-personal), a risky ACP path with ambientGEMINI_API_KEYwas able to drift the saved auth selection away from OAuth and into the API-key lane.The concerning part is not only the runtime fallback. The ACP/non-interactive flow appears able to persist the fallback auth method back into durable local settings for later runs.
I am reporting this as observed behavior that may be unintended. If this is actually expected for ACP/non-interactive flows, then I think there should at least be a way to opt out of durable auth mutation.
Version / environment
v0.38.2v0.38.2is also the current latest GitHub release at the time of filing~/.gemini/settings.jsonsecurity.auth.selectedType = oauth-personalsecurity.auth.enforcedType = oauth-personalWhat we observed
We ran two isolated-HOME stage cases without touching the live
~/.geminidirectory.Case A, hardened path
oauth-personalGEMINI_API_KEYwas still intentionally injected into the staged processResult:
oauth-personalCase B, risky path
GEMINI_API_KEYResult:
~/.gemini/settings.jsonhad drifted toselectedType = gemini-api-keyThis makes the problem look like credential precedence plus durable state mutation, not just an expired cache.
Minimal reproduction shape
I do not yet have a pure-gemini-cli-only repro without an ACP client, but the minimal observed shape was:
HOME~/.gemini/settings.jsonwith:selectedType = oauth-personalenforcedType = oauth-personalGEMINI_API_KEYinto the environment~/.gemini/settings.jsonObserved result in the risky path:
security.auth.selectedTypechanged fromoauth-personaltogemini-api-keyThe exact helper I used on my side was an external ACP client. If helpful, I can provide a smaller reproduction packet for that path.
Why this seems surprising
From the docs,
security.auth.selectedTypereads as the durable selected auth method, andsecurity.auth.enforcedTypereads as the required auth method:docs/reference/configuration.mdI did not find an obvious note in the README/configuration docs saying that ACP/non-interactive fallback auth is expected to rewrite the durable auth selection based on ambient env credentials.
Source seams that look relevant
I realize bundled code can differ from source, so below are the corresponding source-level seams from
mainthat appear relevant.1. ACP authenticate persists the chosen method into durable settings
packages/cli/src/acp/acpClient.ts2. Non-interactive auth validation allows env discovery into the effective auth type
packages/cli/src/validateNonInterActiveAuth.tsThose two together look risky in ACP/non-interactive flows:
selectedTypegets rewrittenExpected behavior
One of these would feel safer:
security.auth.selectedType, orselectedType, orQuestions
acpClient.tspersistence path look like the right fix seam?Thanks. I can provide more exact before/after state and the ACP-side repro details if that would help narrow this further.