Merge pull request #155 from multica-ai/fix/codex-oauth-expiry

fix(oauth): don't expire credentials with refresh_token
This commit is contained in:
Bohan Jiang 2026-02-13 11:56:49 +08:00 committed by GitHub
commit 7acf4cc4a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 13 additions and 19 deletions

View file

@ -176,7 +176,9 @@ export function readClaudeCliCredentials(): ClaudeCliCredential | null {
export function hasValidClaudeCliCredentials(): boolean {
const creds = readClaudeCliCredentials();
if (!creds) return false;
// Check if not expired (with 5 minute buffer)
// OAuth with refresh_token can auto-renew; always valid
if (creds.type === "oauth" && creds.refresh) return true;
// Token-only: check if not expired (with 5 minute buffer)
return creds.expires > Date.now() + 5 * 60 * 1000;
}
@ -214,14 +216,8 @@ function readCodexKeychainCredentials(): CodexCliCredential | null {
if (typeof accessToken !== "string" || !accessToken) return null;
if (typeof refreshToken !== "string" || !refreshToken) return null;
const lastRefreshRaw = parsed.last_refresh;
const lastRefresh =
typeof lastRefreshRaw === "string" || typeof lastRefreshRaw === "number"
? new Date(lastRefreshRaw).getTime()
: Date.now();
const expires = Number.isFinite(lastRefresh)
? lastRefresh + 60 * 60 * 1000
: Date.now() + 60 * 60 * 1000;
// OAuth with refresh_token can auto-renew; don't expire based on last_refresh
const expires = Infinity;
return {
type: "oauth",
@ -252,13 +248,8 @@ function readCodexFileCredentials(): CodexCliCredential | null {
if (typeof accessToken !== "string" || !accessToken) return null;
if (typeof refreshToken !== "string" || !refreshToken) return null;
let expires: number;
try {
const stat = fs.statSync(authPath);
expires = stat.mtimeMs + 60 * 60 * 1000;
} catch {
expires = Date.now() + 60 * 60 * 1000;
}
// OAuth with refresh_token can auto-renew; don't expire based on file mtime
const expires = Infinity;
return {
type: "oauth",
@ -292,6 +283,8 @@ export function readCodexCliCredentials(): CodexCliCredential | null {
export function hasValidCodexCliCredentials(): boolean {
const creds = readCodexCliCredentials();
if (!creds) return false;
// With refresh_token, credentials are always valid (auto-renew)
if (creds.refresh) return true;
return creds.expires > Date.now() + 5 * 60 * 1000;
}
@ -356,6 +349,7 @@ export function getCliCredentialStatus(): CliCredentialStatus[] {
function formatDuration(ms: number): string {
if (ms <= 0) return "expired";
if (!Number.isFinite(ms)) return "never";
const hours = Math.floor(ms / (60 * 60 * 1000));
const minutes = Math.floor((ms % (60 * 60 * 1000)) / (60 * 1000));
if (hours > 0) return `${hours}h ${minutes}m`;

View file

@ -58,8 +58,8 @@ const PROVIDER_REGISTRY: Record<string, ProviderMeta> = {
id: "openai-codex",
name: "Codex (OAuth)",
authMethod: "oauth",
defaultModel: "gpt-5.2",
models: ["gpt-5.2", "gpt-5.2-codex", "gpt-5.1-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5-mini"],
defaultModel: "gpt-5.3-codex",
models: ["gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.2", "gpt-5.1-codex-max", "gpt-5.1-codex-mini"],
loginCommand: "codex login",
},
"anthropic": {
@ -75,7 +75,7 @@ const PROVIDER_REGISTRY: Record<string, ProviderMeta> = {
name: "OpenAI",
authMethod: "api-key",
defaultModel: "gpt-4o",
models: ["gpt-5.2", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini", "o3", "o3-mini"],
models: ["gpt-5.3-codex", "gpt-5.2", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini", "o3", "o3-mini"],
loginUrl: "https://platform.openai.com/api-keys",
},
"kimi-coding": {