From 1ff461fe7b9d148221ba97faf5187023f2c82f25 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Wed, 22 Apr 2026 16:28:52 +0530 Subject: [PATCH] fix(cli): stabilize oauth session auth epochs --- src/agents/cli-auth-epoch.ts | 18 ++++-------------- src/agents/cli-runner.ts | 1 + src/agents/cli-runner/prepare.ts | 17 ++++++++++------- src/agents/cli-runner/types.ts | 1 + src/agents/cli-session.ts | 10 +++++++++- src/config/sessions/types.ts | 1 + 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/agents/cli-auth-epoch.ts b/src/agents/cli-auth-epoch.ts index 0242811da79..a648856d54f 100644 --- a/src/agents/cli-auth-epoch.ts +++ b/src/agents/cli-auth-epoch.ts @@ -23,6 +23,8 @@ const defaultCliAuthEpochDeps: CliAuthEpochDeps = { const cliAuthEpochDeps: CliAuthEpochDeps = { ...defaultCliAuthEpochDeps }; +export const CLI_AUTH_EPOCH_VERSION = 2; + export function setCliAuthEpochTestDeps(overrides: Partial): void { Object.assign(cliAuthEpochDeps, overrides); } @@ -41,24 +43,16 @@ function encodeUnknown(value: unknown): string { function encodeClaudeCredential(credential: ClaudeCliCredential): string { if (credential.type === "oauth") { - return JSON.stringify([ - "oauth", - credential.provider, - credential.access, - credential.refresh, - credential.expires, - ]); + return JSON.stringify(["oauth", credential.provider, credential.refresh]); } - return JSON.stringify(["token", credential.provider, credential.token, credential.expires]); + return JSON.stringify(["token", credential.provider, credential.token]); } function encodeCodexCredential(credential: CodexCliCredential): string { return JSON.stringify([ credential.type, credential.provider, - credential.access, credential.refresh, - credential.expires, credential.accountId ?? null, ]); } @@ -81,7 +75,6 @@ function encodeAuthProfileCredential(credential: AuthProfileCredential): string credential.provider, credential.token ?? null, encodeUnknown(credential.tokenRef), - credential.expires ?? null, credential.email ?? null, credential.displayName ?? null, ]); @@ -89,12 +82,9 @@ function encodeAuthProfileCredential(credential: AuthProfileCredential): string return JSON.stringify([ "oauth", credential.provider, - credential.access, credential.refresh, - credential.expires, credential.clientId ?? null, credential.email ?? null, - credential.displayName ?? null, credential.enterpriseUrl ?? null, credential.projectId ?? null, credential.accountId ?? null, diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts index aba115367b1..e2078eafae9 100644 --- a/src/agents/cli-runner.ts +++ b/src/agents/cli-runner.ts @@ -72,6 +72,7 @@ export async function runPreparedCliAgent( ? { authProfileId: context.effectiveAuthProfileId } : {}), ...(context.authEpoch ? { authEpoch: context.authEpoch } : {}), + authEpochVersion: context.authEpochVersion, ...(context.extraSystemPromptHash ? { extraSystemPromptHash: context.extraSystemPromptHash } : {}), diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index 93bc6265454..4901fbf34e5 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -21,7 +21,7 @@ import { makeBootstrapWarn as makeBootstrapWarnImpl, resolveBootstrapContextForRun as resolveBootstrapContextForRunImpl, } from "../bootstrap-files.js"; -import { resolveCliAuthEpoch } from "../cli-auth-epoch.js"; +import { CLI_AUTH_EPOCH_VERSION, resolveCliAuthEpoch } from "../cli-auth-epoch.js"; import { resolveCliBackendConfig } from "../cli-backends.js"; import { hashCliSessionText, resolveCliSessionReuse } from "../cli-session.js"; import { resolveHeartbeatPromptForSystemPrompt } from "../heartbeat-system-prompt.js"; @@ -188,15 +188,16 @@ export async function prepareCliRunContext( modelId, authProfileId: effectiveAuthProfileId, }); + const skipLocalCredentialEpoch = shouldSkipLocalCliCredentialEpoch({ + authEpochMode: backendResolved.authEpochMode, + authProfileId: effectiveAuthProfileId, + authCredential, + preparedExecution, + }); const authEpoch = await resolveCliAuthEpoch({ provider: params.provider, authProfileId: effectiveAuthProfileId, - skipLocalCredential: shouldSkipLocalCliCredentialEpoch({ - authEpochMode: backendResolved.authEpochMode, - authProfileId: effectiveAuthProfileId, - authCredential, - preparedExecution, - }), + skipLocalCredential: skipLocalCredentialEpoch, }); const preparedBackendEnv = preparedExecution?.env && Object.keys(preparedExecution.env).length > 0 @@ -232,6 +233,7 @@ export async function prepareCliRunContext( binding: params.cliSessionBinding, authProfileId: effectiveAuthProfileId, authEpoch, + authEpochVersion: CLI_AUTH_EPOCH_VERSION, extraSystemPromptHash, mcpConfigHash: preparedBackendFinal.mcpConfigHash, mcpResumeHash: preparedBackendFinal.mcpResumeHash, @@ -332,6 +334,7 @@ export async function prepareCliRunContext( bootstrapPromptWarningLines: bootstrapPromptWarning.lines, heartbeatPrompt, authEpoch, + authEpochVersion: CLI_AUTH_EPOCH_VERSION, extraSystemPromptHash, }; } diff --git a/src/agents/cli-runner/types.ts b/src/agents/cli-runner/types.ts index ba686a666a6..ecae4de5e3e 100644 --- a/src/agents/cli-runner/types.ts +++ b/src/agents/cli-runner/types.ts @@ -68,5 +68,6 @@ export type PreparedCliRunContext = { bootstrapPromptWarningLines: string[]; heartbeatPrompt?: string; authEpoch?: string; + authEpochVersion: number; extraSystemPromptHash?: string; }; diff --git a/src/agents/cli-session.ts b/src/agents/cli-session.ts index 20cd18d6730..d7724d30fef 100644 --- a/src/agents/cli-session.ts +++ b/src/agents/cli-session.ts @@ -28,6 +28,7 @@ export function getCliSessionBinding( sessionId: bindingSessionId, authProfileId: normalizeOptionalString(fromBindings?.authProfileId), authEpoch: normalizeOptionalString(fromBindings?.authEpoch), + authEpochVersion: fromBindings?.authEpochVersion, extraSystemPromptHash: normalizeOptionalString(fromBindings?.extraSystemPromptHash), mcpConfigHash: normalizeOptionalString(fromBindings?.mcpConfigHash), mcpResumeHash: normalizeOptionalString(fromBindings?.mcpResumeHash), @@ -78,6 +79,9 @@ export function setCliSessionBinding( ...(normalizeOptionalString(binding.authEpoch) ? { authEpoch: normalizeOptionalString(binding.authEpoch) } : {}), + ...(typeof binding.authEpochVersion === "number" && Number.isFinite(binding.authEpochVersion) + ? { authEpochVersion: binding.authEpochVersion } + : {}), ...(normalizeOptionalString(binding.extraSystemPromptHash) ? { extraSystemPromptHash: normalizeOptionalString(binding.extraSystemPromptHash) } : {}), @@ -122,6 +126,7 @@ export function resolveCliSessionReuse(params: { binding?: CliSessionBinding; authProfileId?: string; authEpoch?: string; + authEpochVersion?: number; extraSystemPromptHash?: string; mcpConfigHash?: string; mcpResumeHash?: string; @@ -144,7 +149,10 @@ export function resolveCliSessionReuse(params: { return { invalidatedReason: "auth-profile" }; } const storedAuthEpoch = normalizeOptionalString(binding?.authEpoch); - if (storedAuthEpoch !== currentAuthEpoch) { + if ( + binding?.authEpochVersion === params.authEpochVersion && + storedAuthEpoch !== currentAuthEpoch + ) { return { invalidatedReason: "auth-epoch" }; } const storedExtraSystemPromptHash = normalizeOptionalString(binding?.extraSystemPromptHash); diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index 2ac683aa2ea..039b4882c58 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -72,6 +72,7 @@ export type CliSessionBinding = { sessionId: string; authProfileId?: string; authEpoch?: string; + authEpochVersion?: number; extraSystemPromptHash?: string; mcpConfigHash?: string; mcpResumeHash?: string;