From d53e559ae71c3a32f14ae30dcdae8a7e570fc92f Mon Sep 17 00:00:00 2001 From: Shakker Date: Mon, 15 Jun 2026 19:48:29 +0100 Subject: [PATCH] fix: bind Gemini CLI epochs to profile homes --- src/agents/cli-auth-epoch.test.ts | 65 +++++++++++++++++++++++++++++++ src/agents/cli-auth-epoch.ts | 6 ++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/agents/cli-auth-epoch.test.ts b/src/agents/cli-auth-epoch.test.ts index 62d0b411abd..f18d6a2f98c 100644 --- a/src/agents/cli-auth-epoch.test.ts +++ b/src/agents/cli-auth-epoch.test.ts @@ -112,6 +112,71 @@ describe("resolveCliAuthEpoch", () => { }); }); + it("separates Gemini CLI OAuth profile epochs by profile id", async () => { + let access = "access-a"; + let refresh = "refresh-a"; + const store: AuthProfileStore = { + version: 1, + profiles: { + "google-gemini-cli:primary": { + type: "oauth", + provider: "google-gemini-cli", + access, + refresh, + expires: 1, + email: "user@example.test", + accountId: "google-account-1", + projectId: "project-1", + }, + "google-gemini-cli:renamed": { + type: "oauth", + provider: "google-gemini-cli", + access, + refresh, + expires: 1, + email: "user@example.test", + accountId: "google-account-1", + projectId: "project-1", + }, + }, + }; + setCliAuthEpochTestDeps({ + readGeminiCliCredentialsCached: () => null, + loadAuthProfileStoreForRuntime: () => store, + }); + + const primary = await resolveCliAuthEpoch({ + provider: "google-gemini-cli", + agentDir: "/agents/main/agent", + authProfileId: "google-gemini-cli:primary", + skipLocalCredential: true, + }); + access = "access-b"; + refresh = "refresh-b"; + store.profiles["google-gemini-cli:primary"] = { + ...store.profiles["google-gemini-cli:primary"]!, + access, + refresh, + expires: 2, + }; + const primaryAfterRefresh = await resolveCliAuthEpoch({ + provider: "google-gemini-cli", + agentDir: "/agents/main/agent", + authProfileId: "google-gemini-cli:primary", + skipLocalCredential: true, + }); + const renamed = await resolveCliAuthEpoch({ + provider: "google-gemini-cli", + agentDir: "/agents/main/agent", + authProfileId: "google-gemini-cli:renamed", + skipLocalCredential: true, + }); + + expectCliAuthEpoch(primary); + expect(primaryAfterRefresh).toBe(primary); + expect(renamed).not.toBe(primary); + }); + it("keeps identity-less claude cli oauth epochs stable across token changes", async () => { let access = "access-a"; let refresh = "refresh-a"; diff --git a/src/agents/cli-auth-epoch.ts b/src/agents/cli-auth-epoch.ts index dfdac2d959f..f0feab14086 100644 --- a/src/agents/cli-auth-epoch.ts +++ b/src/agents/cli-auth-epoch.ts @@ -32,7 +32,9 @@ const defaultCliAuthEpochDeps: CliAuthEpochDeps = { const cliAuthEpochDeps: CliAuthEpochDeps = { ...defaultCliAuthEpochDeps }; /** Version salt for CLI auth epoch encoding semantics. */ -export const CLI_AUTH_EPOCH_VERSION = 5; +export const CLI_AUTH_EPOCH_VERSION = 6; + +const GEMINI_CLI_PROVIDER_ID = "google-gemini-cli"; /** Overrides credential readers for auth-epoch unit tests. */ export function setCliAuthEpochTestDeps(overrides: Partial): void { @@ -159,7 +161,7 @@ function encodeAuthProfileEpochPart( credential: AuthProfileCredential, ): string { const credentialHash = hashCliAuthEpochPart(encodeAuthProfileCredential(credential)); - if (hasOAuthAccountIdentity(credential)) { + if (hasOAuthAccountIdentity(credential) && credential.provider !== GEMINI_CLI_PROVIDER_ID) { return `profile:oauth-identity:${credentialHash}`; } return `profile:${authProfileId}:${credentialHash}`;