fix(cli): stabilize oauth session auth epochs

This commit is contained in:
Ayaan Zaidi
2026-04-22 16:28:52 +05:30
parent 8778521167
commit 1ff461fe7b
6 changed files with 26 additions and 22 deletions

View File

@@ -23,6 +23,8 @@ const defaultCliAuthEpochDeps: CliAuthEpochDeps = {
const cliAuthEpochDeps: CliAuthEpochDeps = { ...defaultCliAuthEpochDeps };
export const CLI_AUTH_EPOCH_VERSION = 2;
export function setCliAuthEpochTestDeps(overrides: Partial<CliAuthEpochDeps>): 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,

View File

@@ -72,6 +72,7 @@ export async function runPreparedCliAgent(
? { authProfileId: context.effectiveAuthProfileId }
: {}),
...(context.authEpoch ? { authEpoch: context.authEpoch } : {}),
authEpochVersion: context.authEpochVersion,
...(context.extraSystemPromptHash
? { extraSystemPromptHash: context.extraSystemPromptHash }
: {}),

View File

@@ -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,
};
}

View File

@@ -68,5 +68,6 @@ export type PreparedCliRunContext = {
bootstrapPromptWarningLines: string[];
heartbeatPrompt?: string;
authEpoch?: string;
authEpochVersion: number;
extraSystemPromptHash?: string;
};

View File

@@ -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);

View File

@@ -72,6 +72,7 @@ export type CliSessionBinding = {
sessionId: string;
authProfileId?: string;
authEpoch?: string;
authEpochVersion?: number;
extraSystemPromptHash?: string;
mcpConfigHash?: string;
mcpResumeHash?: string;