From e5432ec4e1685a78ca7251bc71f26c1f17355a15 Mon Sep 17 00:00:00 2001 From: George Pickett Date: Sat, 14 Mar 2026 15:29:19 -0700 Subject: [PATCH] fix: sync codex cli auth into default profile --- CHANGELOG.md | 1 + src/agents/auth-profiles.external-cli-sync.test.ts | 7 +++++-- src/agents/auth-profiles/external-cli-sync.ts | 5 +++-- .../doctor-auth.deprecated-cli-profiles.test.ts | 14 ++++++++++++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0da6d6e8cc..031a35d6264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ Docs: https://docs.openclaw.ai - Gateway/websocket pairing bypass for disabled auth: skip device-pairing enforcement when `gateway.auth.mode=none` so Control UI connections behind reverse proxies no longer get stuck on `pairing required` (code 1008) despite auth being explicitly disabled. (#42931) - Auth/login lockout recovery: clear stale `auth_permanent` and `billing` disabled state for all profiles matching the target provider when `openclaw models auth login` is invoked, so users locked out by expired or revoked OAuth tokens can recover by re-authenticating instead of waiting for the cooldown timer to expire. (#43057) - Auto-reply/context-engine compaction: persist the exact embedded-run metadata compaction count for main and followup runner session accounting, so metadata-only auto-compactions no longer undercount multi-compaction runs. (#42629) thanks @uf-hy. +- Auth/Codex CLI reuse: sync reused Codex CLI credentials into the supported `openai-codex:default` OAuth profile instead of reviving the deprecated `openai-codex:codex-cli` slot, so doctor cleanup no longer loops. (#45353) thanks @Gugu-sugar. ## 2026.3.12 diff --git a/src/agents/auth-profiles.external-cli-sync.test.ts b/src/agents/auth-profiles.external-cli-sync.test.ts index 0e1d618efa1..303b85b72d2 100644 --- a/src/agents/auth-profiles.external-cli-sync.test.ts +++ b/src/agents/auth-profiles.external-cli-sync.test.ts @@ -16,8 +16,10 @@ vi.mock("./cli-credentials.js", () => ({ const { syncExternalCliCredentials } = await import("./auth-profiles/external-cli-sync.js"); const { CODEX_CLI_PROFILE_ID } = await import("./auth-profiles/constants.js"); +const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai-codex:default"; + describe("syncExternalCliCredentials", () => { - it("syncs Codex CLI credentials into the compatibility auth profile", () => { + it("syncs Codex CLI credentials into the supported default auth profile", () => { const expires = Date.now() + 60_000; mocks.readCodexCliCredentialsCached.mockReturnValue({ type: "oauth", @@ -39,7 +41,7 @@ describe("syncExternalCliCredentials", () => { expect(mocks.readCodexCliCredentialsCached).toHaveBeenCalledWith( expect.objectContaining({ ttlMs: expect.any(Number) }), ); - expect(store.profiles[CODEX_CLI_PROFILE_ID]).toMatchObject({ + expect(store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID]).toMatchObject({ type: "oauth", provider: "openai-codex", access: "access-token", @@ -47,5 +49,6 @@ describe("syncExternalCliCredentials", () => { expires, accountId: "acct_123", }); + expect(store.profiles[CODEX_CLI_PROFILE_ID]).toBeUndefined(); }); }); diff --git a/src/agents/auth-profiles/external-cli-sync.ts b/src/agents/auth-profiles/external-cli-sync.ts index d467da879a1..2627845ed40 100644 --- a/src/agents/auth-profiles/external-cli-sync.ts +++ b/src/agents/auth-profiles/external-cli-sync.ts @@ -4,7 +4,6 @@ import { readMiniMaxCliCredentialsCached, } from "../cli-credentials.js"; import { - CODEX_CLI_PROFILE_ID, EXTERNAL_CLI_NEAR_EXPIRY_MS, EXTERNAL_CLI_SYNC_TTL_MS, QWEN_CLI_PROFILE_ID, @@ -13,6 +12,8 @@ import { } from "./constants.js"; import type { AuthProfileCredential, AuthProfileStore, OAuthCredential } from "./types.js"; +const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai-codex:default"; + function shallowEqualOAuthCredentials(a: OAuthCredential | undefined, b: OAuthCredential): boolean { if (!a) { return false; @@ -140,7 +141,7 @@ export function syncExternalCliCredentials(store: AuthProfileStore): boolean { if ( syncExternalCliCredentialsForProvider( store, - CODEX_CLI_PROFILE_ID, + OPENAI_CODEX_DEFAULT_PROFILE_ID, "openai-codex", () => readCodexCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS }), now, diff --git a/src/commands/doctor-auth.deprecated-cli-profiles.test.ts b/src/commands/doctor-auth.deprecated-cli-profiles.test.ts index d6436d7027a..ec7e824cd9e 100644 --- a/src/commands/doctor-auth.deprecated-cli-profiles.test.ts +++ b/src/commands/doctor-auth.deprecated-cli-profiles.test.ts @@ -63,6 +63,13 @@ describe("maybeRemoveDeprecatedCliAuthProfiles", () => { refresh: "token-r2", expires: Date.now() + 60_000, }, + "openai-codex:default": { + type: "oauth", + provider: "openai-codex", + access: "token-c", + refresh: "token-r3", + expires: Date.now() + 60_000, + }, }, }, null, @@ -76,10 +83,11 @@ describe("maybeRemoveDeprecatedCliAuthProfiles", () => { profiles: { "anthropic:claude-cli": { provider: "anthropic", mode: "oauth" }, "openai-codex:codex-cli": { provider: "openai-codex", mode: "oauth" }, + "openai-codex:default": { provider: "openai-codex", mode: "oauth" }, }, order: { anthropic: ["anthropic:claude-cli"], - "openai-codex": ["openai-codex:codex-cli"], + "openai-codex": ["openai-codex:codex-cli", "openai-codex:default"], }, }, } as const; @@ -94,10 +102,12 @@ describe("maybeRemoveDeprecatedCliAuthProfiles", () => { }; expect(raw.profiles?.["anthropic:claude-cli"]).toBeUndefined(); expect(raw.profiles?.["openai-codex:codex-cli"]).toBeUndefined(); + expect(raw.profiles?.["openai-codex:default"]).toBeDefined(); expect(next.auth?.profiles?.["anthropic:claude-cli"]).toBeUndefined(); expect(next.auth?.profiles?.["openai-codex:codex-cli"]).toBeUndefined(); + expect(next.auth?.profiles?.["openai-codex:default"]).toBeDefined(); expect(next.auth?.order?.anthropic).toBeUndefined(); - expect(next.auth?.order?.["openai-codex"]).toBeUndefined(); + expect(next.auth?.order?.["openai-codex"]).toEqual(["openai-codex:default"]); }); });