diff --git a/docs/reference/wizard.md b/docs/reference/wizard.md
index 76d5bd61b5b..d90c0473ca1 100644
--- a/docs/reference/wizard.md
+++ b/docs/reference/wizard.md
@@ -33,9 +33,10 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard).
- **Anthropic API key**: uses `ANTHROPIC_API_KEY` if present or prompts for a key, then saves it for daemon use.
- **Anthropic API key**: preferred Anthropic assistant choice in onboarding/configure.
- **Anthropic setup-token**: still available in onboarding/configure, though OpenClaw now prefers Claude CLI reuse when available.
- - **OpenAI Code (Codex) subscription (Codex CLI)**: if `~/.codex/auth.json` exists, onboarding can reuse it. Reused Codex CLI credentials stay managed by Codex CLI; on expiry OpenClaw re-reads that source first and, when the provider can refresh it, writes the refreshed credential back to Codex storage instead of taking ownership itself.
- **OpenAI Code (Codex) subscription (OAuth)**: browser flow; paste the `code#state`.
- Sets `agents.defaults.model` to `openai-codex/gpt-5.4` when model is unset or `openai/*`.
+ - **OpenAI Code (Codex) subscription (device pairing)**: browser pairing flow with a short-lived device code.
+ - Sets `agents.defaults.model` to `openai-codex/gpt-5.4` when model is unset or `openai/*`.
- **OpenAI API key**: uses `OPENAI_API_KEY` if present or prompts for a key, then stores it in auth profiles.
- Sets `agents.defaults.model` to `openai/gpt-5.4` when model is unset, `openai/*`, or `openai-codex/*`.
- **xAI (Grok) API key**: prompts for `XAI_API_KEY` and configures xAI as a model provider.
diff --git a/docs/start/wizard-cli-reference.md b/docs/start/wizard-cli-reference.md
index 14a8d20f4b4..336a4d0208b 100644
--- a/docs/start/wizard-cli-reference.md
+++ b/docs/start/wizard-cli-reference.md
@@ -129,18 +129,17 @@ What you set:
Uses `ANTHROPIC_API_KEY` if present or prompts for a key, then saves it for daemon use.
-
- If `~/.codex/auth.json` exists, the wizard can reuse it.
- Reused Codex CLI credentials stay managed by Codex CLI; on expiry OpenClaw
- re-reads that source first and, when the provider can refresh it, writes
- the refreshed credential back to Codex storage instead of taking ownership
- itself.
-
Browser flow; paste `code#state`.
Sets `agents.defaults.model` to `openai-codex/gpt-5.4` when model is unset or `openai/*`.
+
+
+ Browser pairing flow with a short-lived device code.
+
+ Sets `agents.defaults.model` to `openai-codex/gpt-5.4` when model is unset or `openai/*`.
+
Uses `OPENAI_API_KEY` if present or prompts for a key, then stores the credential in auth profiles.
diff --git a/extensions/openai/cli-backend.ts b/extensions/openai/cli-backend.ts
index 3aa597b7d1d..d4771ecf75e 100644
--- a/extensions/openai/cli-backend.ts
+++ b/extensions/openai/cli-backend.ts
@@ -3,9 +3,9 @@ import {
CLI_FRESH_WATCHDOG_DEFAULTS,
CLI_RESUME_WATCHDOG_DEFAULTS,
} from "openclaw/plugin-sdk/cli-backend";
-import { OPENAI_CODEX_DEFAULT_PROFILE_ID } from "./openai-codex-cli-auth.js";
import { prepareOpenAICodexCliExecution } from "./openai-codex-cli-bridge.js";
+const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai-codex:default";
const CODEX_CLI_DEFAULT_MODEL_REF = "codex-cli/gpt-5.4";
export function buildOpenAICodexCliBackend(): CliBackendPlugin {
diff --git a/extensions/openai/openai-codex-cli-auth.test.ts b/extensions/openai/openai-codex-cli-auth.test.ts
deleted file mode 100644
index 9954221a77f..00000000000
--- a/extensions/openai/openai-codex-cli-auth.test.ts
+++ /dev/null
@@ -1,311 +0,0 @@
-import fs from "node:fs";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-
-const runtimeMocks = vi.hoisted(() => ({
- debug: vi.fn(),
-}));
-
-vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
- createSubsystemLogger: () => ({
- debug: runtimeMocks.debug,
- }),
-}));
-
-import {
- OPENAI_CODEX_DEFAULT_PROFILE_ID,
- hasOpenAICodexCliOAuthCredential,
- readOpenAICodexCliOAuthProfile,
-} from "./openai-codex-cli-auth.js";
-
-function buildJwt(payload: Record) {
- const encode = (value: Record) =>
- Buffer.from(JSON.stringify(value)).toString("base64url");
- return `${encode({ alg: "none", typ: "JWT" })}.${encode(payload)}.sig`;
-}
-
-function mockCodexCliChatGptAuth(params?: {
- email?: string;
- accountId?: string;
- accessToken?: string;
-}) {
- const accessToken =
- params?.accessToken ??
- buildJwt({
- exp: Math.floor(Date.now() / 1000) + 600,
- "https://api.openai.com/profile": {
- email: params?.email ?? "codex@example.com",
- },
- });
- vi.spyOn(fs, "readFileSync").mockReturnValue(
- JSON.stringify({
- auth_mode: "chatgpt",
- tokens: {
- id_token: "id-token",
- access_token: accessToken,
- refresh_token: "refresh-token",
- account_id: params?.accountId ?? "acct_123",
- },
- }),
- );
- return accessToken;
-}
-
-describe("readOpenAICodexCliOAuthProfile", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
-
- afterEach(() => {
- vi.restoreAllMocks();
- });
-
- it("reads Codex CLI chatgpt auth into the default OpenAI Codex profile", () => {
- const accessToken = mockCodexCliChatGptAuth();
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: { version: 1, profiles: {} },
- });
-
- expect(parsed).toMatchObject({
- profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID,
- credential: {
- type: "oauth",
- provider: "openai-codex",
- access: accessToken,
- refresh: "refresh-token",
- accountId: "acct_123",
- idToken: "id-token",
- email: "codex@example.com",
- },
- });
- expect(parsed?.credential.expires).toBeGreaterThan(Date.now());
- });
-
- it("detects an existing Codex CLI chatgpt login for setup labeling", () => {
- vi.spyOn(fs, "readFileSync").mockReturnValue(
- JSON.stringify({
- auth_mode: "chatgpt",
- tokens: {
- access_token: "access-token",
- refresh_token: "refresh-token",
- },
- }),
- );
-
- expect(hasOpenAICodexCliOAuthCredential()).toBe(true);
- });
-
- it("does not override a locally managed OpenAI Codex profile", () => {
- vi.spyOn(fs, "readFileSync").mockReturnValue(
- JSON.stringify({
- auth_mode: "chatgpt",
- tokens: {
- access_token: "access-token",
- refresh_token: "refresh-token",
- },
- }),
- );
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: {
- version: 1,
- profiles: {
- [OPENAI_CODEX_DEFAULT_PROFILE_ID]: {
- type: "oauth",
- provider: "openai-codex",
- access: "local-access",
- refresh: "local-refresh",
- expires: Date.now() + 10 * 60_000,
- },
- },
- },
- });
-
- expect(parsed).toBeNull();
- });
-
- it("does not override explicit local non-oauth auth with Codex CLI bootstrap", () => {
- vi.spyOn(fs, "readFileSync").mockReturnValue(
- JSON.stringify({
- auth_mode: "chatgpt",
- tokens: {
- access_token: "access-token",
- refresh_token: "refresh-token",
- },
- }),
- );
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: {
- version: 1,
- profiles: {
- [OPENAI_CODEX_DEFAULT_PROFILE_ID]: {
- type: "api_key",
- provider: "openai-codex",
- key: "sk-local",
- },
- },
- },
- });
-
- expect(parsed).toBeNull();
- });
-
- it("refuses Codex CLI bootstrap when an expired local default belongs to a different account", () => {
- mockCodexCliChatGptAuth({
- email: "codex-b@example.com",
- accountId: "acct_b",
- });
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: {
- version: 1,
- profiles: {
- [OPENAI_CODEX_DEFAULT_PROFILE_ID]: {
- type: "oauth",
- provider: "openai-codex",
- access: "near-expiry-local-access",
- refresh: "near-expiry-local-refresh",
- expires: Date.now() + 60_000,
- accountId: "acct_a",
- email: "codex-a@example.com",
- },
- },
- },
- });
-
- expect(parsed).toBeNull();
- });
-
- it("allows cli bootstrap when the stored default profile is expired", () => {
- const accessToken = mockCodexCliChatGptAuth();
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: {
- version: 1,
- profiles: {
- [OPENAI_CODEX_DEFAULT_PROFILE_ID]: {
- type: "oauth",
- provider: "openai-codex",
- access: "expired-local-access",
- refresh: "expired-local-refresh",
- expires: Date.now() - 60_000,
- accountId: "acct_123",
- },
- },
- },
- });
-
- expect(parsed).toMatchObject({
- profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID,
- credential: {
- access: accessToken,
- refresh: "refresh-token",
- accountId: "acct_123",
- idToken: "id-token",
- email: "codex@example.com",
- },
- });
- });
-
- it("refuses cli bootstrap when the stored default profile is expired but identity mismatches", () => {
- mockCodexCliChatGptAuth();
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: {
- version: 1,
- profiles: {
- [OPENAI_CODEX_DEFAULT_PROFILE_ID]: {
- type: "oauth",
- provider: "openai-codex",
- access: "expired-local-access",
- refresh: "expired-local-refresh",
- expires: Date.now() - 60_000,
- accountId: "acct_local",
- },
- },
- },
- });
-
- expect(parsed).toBeNull();
- });
-
- it("allows the runtime-only Codex CLI profile when the stored default already matches", () => {
- const accessToken = mockCodexCliChatGptAuth();
-
- const firstParse = readOpenAICodexCliOAuthProfile({
- store: { version: 1, profiles: {} },
- });
- expect(firstParse).not.toBeNull();
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: {
- version: 1,
- profiles: {
- [OPENAI_CODEX_DEFAULT_PROFILE_ID]: firstParse!.credential,
- },
- },
- });
-
- expect(parsed).toMatchObject({
- profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID,
- credential: {
- access: accessToken,
- refresh: "refresh-token",
- accountId: "acct_123",
- idToken: "id-token",
- email: "codex@example.com",
- },
- });
- });
-
- it("returns null without logging when the Codex CLI auth file is missing", () => {
- const error = Object.assign(new Error("missing"), {
- code: "ENOENT",
- });
- vi.spyOn(fs, "readFileSync").mockImplementation(() => {
- throw error;
- });
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: { version: 1, profiles: {} },
- });
-
- expect(parsed).toBeNull();
- expect(runtimeMocks.debug).not.toHaveBeenCalled();
- });
-
- it("logs a sanitized code for invalid auth JSON", () => {
- vi.spyOn(fs, "readFileSync").mockReturnValue("{");
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: { version: 1, profiles: {} },
- });
-
- expect(parsed).toBeNull();
- expect(runtimeMocks.debug).toHaveBeenCalledWith(
- "Failed to read Codex CLI auth file (code=INVALID_JSON)",
- );
- });
-
- it("does not leak auth file paths in debug logs for filesystem failures", () => {
- const error = Object.assign(
- new Error("EACCES: permission denied, open '/Users/alice/.codex/auth.json'"),
- {
- code: "EACCES",
- },
- );
- vi.spyOn(fs, "readFileSync").mockImplementation(() => {
- throw error;
- });
-
- const parsed = readOpenAICodexCliOAuthProfile({
- store: { version: 1, profiles: {} },
- });
-
- expect(parsed).toBeNull();
- expect(runtimeMocks.debug).toHaveBeenCalledWith(
- "Failed to read Codex CLI auth file (code=EACCES)",
- );
- });
-});
diff --git a/extensions/openai/openai-codex-cli-auth.ts b/extensions/openai/openai-codex-cli-auth.ts
deleted file mode 100644
index 859bc565b42..00000000000
--- a/extensions/openai/openai-codex-cli-auth.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-import fs from "node:fs";
-import path from "node:path";
-import {
- hasUsableOAuthCredential,
- resolveRequiredHomeDir,
- type AuthProfileStore,
- type OAuthCredential,
-} from "openclaw/plugin-sdk/provider-auth";
-import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
-import {
- resolveCodexAccessTokenExpiry,
- resolveCodexAuthIdentity,
-} from "./openai-codex-auth-identity.js";
-import { trimNonEmptyString } from "./openai-codex-shared.js";
-
-const PROVIDER_ID = "openai-codex";
-const log = createSubsystemLogger("openai/codex-cli-auth");
-
-export const CODEX_CLI_PROFILE_ID = `${PROVIDER_ID}:codex-cli`;
-export const OPENAI_CODEX_DEFAULT_PROFILE_ID = `${PROVIDER_ID}:default`;
-
-type CodexCliAuthFile = {
- auth_mode?: unknown;
- tokens?: {
- id_token?: unknown;
- access_token?: unknown;
- refresh_token?: unknown;
- account_id?: unknown;
- };
-};
-
-function resolveCodexCliHome(env: NodeJS.ProcessEnv): string {
- const configured = trimNonEmptyString(env.CODEX_HOME);
- if (!configured) {
- return path.join(resolveRequiredHomeDir(), ".codex");
- }
- if (configured === "~") {
- return resolveRequiredHomeDir();
- }
- if (configured.startsWith("~/")) {
- return path.join(resolveRequiredHomeDir(), configured.slice(2));
- }
- return path.resolve(configured);
-}
-
-function readCodexCliAuthFile(env: NodeJS.ProcessEnv): CodexCliAuthFile | null {
- try {
- const authPath = path.join(resolveCodexCliHome(env), "auth.json");
- const raw = fs.readFileSync(authPath, "utf8");
- const parsed = JSON.parse(raw);
- return parsed && typeof parsed === "object" ? (parsed as CodexCliAuthFile) : null;
- } catch (error) {
- const code =
- error instanceof SyntaxError
- ? "INVALID_JSON"
- : error instanceof Error && "code" in error
- ? (error as NodeJS.ErrnoException).code
- : undefined;
- if (code === "ENOENT") {
- return null;
- }
- log.debug(
- `Failed to read Codex CLI auth file (code=${typeof code === "string" ? code : "UNKNOWN"})`,
- );
- return null;
- }
-}
-
-export function hasOpenAICodexCliOAuthCredential(params?: { env?: NodeJS.ProcessEnv }): boolean {
- const authFile = readCodexCliAuthFile(params?.env ?? process.env);
- if (!authFile || authFile.auth_mode !== "chatgpt") {
- return false;
- }
-
- return Boolean(
- trimNonEmptyString(authFile.tokens?.access_token) &&
- trimNonEmptyString(authFile.tokens?.refresh_token),
- );
-}
-
-function oauthCredentialMatches(a: OAuthCredential, b: OAuthCredential): boolean {
- return (
- a.type === b.type &&
- a.provider === b.provider &&
- a.access === b.access &&
- a.refresh === b.refresh &&
- a.clientId === b.clientId &&
- a.email === b.email &&
- a.displayName === b.displayName &&
- a.enterpriseUrl === b.enterpriseUrl &&
- a.projectId === b.projectId &&
- a.accountId === b.accountId &&
- a.idToken === b.idToken
- );
-}
-
-function normalizeAuthIdentityToken(value: string | undefined): string | undefined {
- const trimmed = value?.trim();
- return trimmed ? trimmed : undefined;
-}
-
-function normalizeAuthEmailToken(value: string | undefined): string | undefined {
- return normalizeAuthIdentityToken(value)?.toLowerCase();
-}
-
-function hasIdentityContinuity(
- existing: Pick | undefined,
- incoming: OAuthCredential,
-): boolean {
- if (!existing) {
- return true;
- }
- if (oauthCredentialMatches(existing as OAuthCredential, incoming)) {
- return true;
- }
-
- const existingAccountId = normalizeAuthIdentityToken(existing.accountId);
- const incomingAccountId = normalizeAuthIdentityToken(incoming.accountId);
- if (existingAccountId !== undefined && incomingAccountId !== undefined) {
- return existingAccountId === incomingAccountId;
- }
-
- const existingEmail = normalizeAuthEmailToken(existing.email);
- const incomingEmail = normalizeAuthEmailToken(incoming.email);
- if (existingEmail !== undefined && incomingEmail !== undefined) {
- return existingEmail === incomingEmail;
- }
-
- return false;
-}
-
-export function readOpenAICodexCliOAuthProfile(params: {
- env?: NodeJS.ProcessEnv;
- store: AuthProfileStore;
-}): { profileId: string; credential: OAuthCredential } | null {
- const authFile = readCodexCliAuthFile(params.env ?? process.env);
- if (!authFile || authFile.auth_mode !== "chatgpt") {
- return null;
- }
-
- const access = trimNonEmptyString(authFile.tokens?.access_token);
- const refresh = trimNonEmptyString(authFile.tokens?.refresh_token);
- if (!access || !refresh) {
- return null;
- }
-
- const accountId = trimNonEmptyString(authFile.tokens?.account_id);
- const idToken = trimNonEmptyString(authFile.tokens?.id_token);
- const identity = resolveCodexAuthIdentity({ accessToken: access });
- const credential: OAuthCredential = {
- type: "oauth",
- provider: PROVIDER_ID,
- access,
- refresh,
- expires: resolveCodexAccessTokenExpiry(access) ?? 0,
- ...(accountId ? { accountId } : {}),
- ...(idToken ? { idToken } : {}),
- ...(identity.email ? { email: identity.email } : {}),
- ...(identity.profileName ? { displayName: identity.profileName } : {}),
- };
- const existing = params.store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID];
- const existingOAuth =
- existing?.type === "oauth" && existing.provider === PROVIDER_ID ? existing : undefined;
- if (existing && !existingOAuth) {
- log.debug("kept explicit local auth over Codex CLI bootstrap", {
- profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID,
- localType: existing.type,
- localProvider: existing.provider,
- });
- return null;
- }
- if (!hasIdentityContinuity(existingOAuth, credential)) {
- return null;
- }
- if (
- existingOAuth &&
- hasUsableOAuthCredential(existingOAuth) &&
- !oauthCredentialMatches(existingOAuth, credential)
- ) {
- return null;
- }
-
- return {
- profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID,
- credential,
- };
-}
diff --git a/extensions/openai/openai-codex-provider.test.ts b/extensions/openai/openai-codex-provider.test.ts
index a31f7375e9d..2f7955b086e 100644
--- a/extensions/openai/openai-codex-provider.test.ts
+++ b/extensions/openai/openai-codex-provider.test.ts
@@ -1,32 +1,17 @@
-import fs from "node:fs/promises";
-import os from "node:os";
-import path from "node:path";
-import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const refreshOpenAICodexTokenMock = vi.hoisted(() => vi.fn());
-const readOpenAICodexCliOAuthProfileMock = vi.hoisted(() => vi.fn());
-const hasOpenAICodexCliOAuthCredentialMock = vi.hoisted(() => vi.fn());
const loginOpenAICodexDeviceCodeMock = vi.hoisted(() => vi.fn());
vi.mock("./openai-codex-provider.runtime.js", () => ({
refreshOpenAICodexToken: refreshOpenAICodexTokenMock,
}));
-vi.mock("./openai-codex-cli-auth.js", async (importOriginal) => {
- const actual = await importOriginal();
- return {
- ...actual,
- hasOpenAICodexCliOAuthCredential: hasOpenAICodexCliOAuthCredentialMock,
- readOpenAICodexCliOAuthProfile: readOpenAICodexCliOAuthProfileMock,
- };
-});
-
vi.mock("./openai-codex-device-code.js", () => ({
loginOpenAICodexDeviceCode: loginOpenAICodexDeviceCodeMock,
}));
let buildOpenAICodexProviderPlugin: typeof import("./openai-codex-provider.js").buildOpenAICodexProviderPlugin;
-const tempDirs: string[] = [];
function createCodexTemplate(overrides: {
id?: string;
@@ -67,18 +52,9 @@ describe("openai codex provider", () => {
beforeEach(() => {
refreshOpenAICodexTokenMock.mockReset();
- readOpenAICodexCliOAuthProfileMock.mockReset();
- hasOpenAICodexCliOAuthCredentialMock.mockReset();
- hasOpenAICodexCliOAuthCredentialMock.mockReturnValue(false);
loginOpenAICodexDeviceCodeMock.mockReset();
});
- afterEach(async () => {
- await Promise.all(
- tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })),
- );
- });
-
it("falls back to the cached credential when accountId extraction fails", async () => {
const provider = buildOpenAICodexProviderPlugin();
const credential = {
@@ -149,14 +125,10 @@ describe("openai codex provider", () => {
);
});
- it("offers OpenAI menu auth methods for login, import, and device pairing", () => {
+ it("offers OpenAI menu auth methods for browser login and device pairing", () => {
const provider = buildOpenAICodexProviderPlugin();
- expect(provider.auth?.map((method) => method.id)).toEqual([
- "oauth",
- "device-code",
- "import-codex-cli",
- ]);
+ expect(provider.auth?.map((method) => method.id)).toEqual(["oauth", "device-code"]);
expect(provider.auth?.find((method) => method.id === "oauth")).toMatchObject({
label: "OpenAI Codex Browser Login",
hint: "Sign in with OpenAI in your browser",
@@ -176,65 +148,6 @@ describe("openai codex provider", () => {
assistantPriority: -10,
},
});
- expect(provider.auth?.find((method) => method.id === "import-codex-cli")).toMatchObject({
- label: "Import Existing Codex Login",
- hint: "Import an existing ~/.codex login",
- kind: "oauth",
- wizard: {
- choiceId: "openai-codex-import",
- choiceLabel: "Import Existing Codex Login",
- assistantPriority: -20,
- assistantVisibility: "manual-only",
- },
- });
- });
-
- it("annotates the import option when ~/.codex auth is detected", () => {
- hasOpenAICodexCliOAuthCredentialMock.mockReturnValueOnce(true);
-
- const provider = buildOpenAICodexProviderPlugin();
-
- expect(provider.auth?.find((method) => method.id === "import-codex-cli")).toMatchObject({
- label: "Import Existing Codex Login (~/.codex detected)",
- wizard: {
- choiceLabel: "Import Existing Codex Login (~/.codex detected)",
- assistantVisibility: "visible",
- },
- });
- });
-
- it("soft-fails import when no compatible ~/.codex login exists", async () => {
- const provider = buildOpenAICodexProviderPlugin();
- const importMethod = provider.auth?.find((method) => method.id === "import-codex-cli");
- const note = vi.fn(async () => {});
- const runtime = {
- log: vi.fn(),
- error: vi.fn(),
- exit: vi.fn(),
- };
- readOpenAICodexCliOAuthProfileMock.mockReturnValueOnce(null);
-
- const result = await importMethod?.run({
- config: {},
- env: process.env,
- prompter: {
- note,
- progress: vi.fn(),
- } as never,
- runtime: runtime as never,
- isRemote: false,
- openUrl: async () => {},
- oauth: { createVpsAwareHandlers: (() => ({})) as never },
- });
-
- expect(result).toEqual({ profiles: [] });
- expect(runtime.error).toHaveBeenCalledWith(
- "No compatible ~/.codex ChatGPT login found. Use Browser Login or Device Pairing instead.",
- );
- expect(note).toHaveBeenCalledWith(
- "No compatible ~/.codex ChatGPT login found. Use Browser Login or Device Pairing instead.",
- "Import Existing Codex Login",
- );
});
it("stores device-code logins as OpenAI Codex oauth profiles", async () => {
@@ -343,89 +256,6 @@ describe("openai codex provider", () => {
);
});
- it("exposes Codex CLI auth as a runtime-only external profile", () => {
- const provider = buildOpenAICodexProviderPlugin();
- const credential = {
- type: "oauth" as const,
- provider: "openai-codex",
- access: "access-token",
- refresh: "refresh-token",
- expires: Date.now() + 60_000,
- accountId: "acct-123",
- };
- readOpenAICodexCliOAuthProfileMock.mockReturnValueOnce({
- profileId: "openai-codex:default",
- credential,
- });
-
- expect(
- provider.resolveExternalAuthProfiles?.({
- env: { CODEX_HOME: "/sandboxed/codex-home" } as NodeJS.ProcessEnv,
- store: { version: 1, profiles: {} },
- }),
- ).toEqual([
- {
- profileId: "openai-codex:default",
- credential,
- persistence: "runtime-only",
- },
- ]);
- expect(readOpenAICodexCliOAuthProfileMock).toHaveBeenCalledWith(
- expect.objectContaining({
- env: expect.objectContaining({ CODEX_HOME: "/sandboxed/codex-home" }),
- store: { version: 1, profiles: {} },
- }),
- );
- });
-
- it("uses the provider auth context env when importing Codex CLI auth", async () => {
- const provider = buildOpenAICodexProviderPlugin();
- const importMethod = provider.auth?.find((method) => method.id === "import-codex-cli");
- const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-openai-codex-provider-"));
- tempDirs.push(agentDir);
- readOpenAICodexCliOAuthProfileMock.mockImplementationOnce(({ env }) => {
- expect(env).toMatchObject({
- CODEX_HOME: "/sandboxed/codex-home",
- });
- return {
- profileId: "openai-codex:default",
- credential: {
- type: "oauth",
- provider: "openai-codex",
- access: "access-token",
- refresh: "refresh-token",
- expires: Date.now() + 60_000,
- email: "codex@example.com",
- displayName: "Codex User",
- accountId: "acct-123",
- },
- };
- });
-
- await expect(
- importMethod?.run({
- config: {},
- env: { CODEX_HOME: "/sandboxed/codex-home" },
- agentDir,
- prompter: {} as never,
- runtime: {} as never,
- isRemote: false,
- openUrl: async () => {},
- oauth: { createVpsAwareHandlers: (() => ({})) as never },
- }),
- ).resolves.toMatchObject({
- profiles: [
- {
- profileId: "openai-codex:default",
- credential: expect.objectContaining({
- provider: "openai-codex",
- access: "access-token",
- }),
- },
- ],
- });
- });
-
it("owns native reasoning output mode for Codex responses", () => {
const provider = buildOpenAICodexProviderPlugin();
diff --git a/extensions/openai/openai-codex-provider.ts b/extensions/openai/openai-codex-provider.ts
index 49cf0f4ff45..246593e3a6d 100644
--- a/extensions/openai/openai-codex-provider.ts
+++ b/extensions/openai/openai-codex-provider.ts
@@ -8,7 +8,6 @@ import {
ensureAuthProfileStoreForLocalUpdate,
listProfilesForProvider,
type OAuthCredential,
- type ProviderAuthResult,
} from "openclaw/plugin-sdk/provider-auth";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
import { loginOpenAICodexOAuth } from "openclaw/plugin-sdk/provider-auth-login";
@@ -24,11 +23,6 @@ import { isOpenAIApiBaseUrl, isOpenAICodexBaseUrl } from "./base-url.js";
import { OPENAI_CODEX_DEFAULT_MODEL } from "./default-models.js";
import { resolveCodexAuthIdentity } from "./openai-codex-auth-identity.js";
import { buildOpenAICodexProvider } from "./openai-codex-catalog.js";
-import {
- CODEX_CLI_PROFILE_ID,
- hasOpenAICodexCliOAuthCredential,
- readOpenAICodexCliOAuthProfile,
-} from "./openai-codex-cli-auth.js";
import { loginOpenAICodexDeviceCode } from "./openai-codex-device-code.js";
import {
buildOpenAIResponsesProviderHooks,
@@ -45,14 +39,11 @@ const OPENAI_WIZARD_GROUP = {
groupLabel: "OpenAI",
groupHint: "API key + Codex auth",
} as const;
+const CODEX_CLI_PROFILE_ID = `${PROVIDER_ID}:codex-cli`;
const OPENAI_CODEX_LOGIN_ASSISTANT_PRIORITY = -30;
-const OPENAI_CODEX_IMPORT_ASSISTANT_PRIORITY = -20;
const OPENAI_CODEX_DEVICE_PAIRING_ASSISTANT_PRIORITY = -10;
const OPENAI_CODEX_LOGIN_LABEL = "OpenAI Codex Browser Login";
const OPENAI_CODEX_LOGIN_HINT = "Sign in with OpenAI in your browser";
-const OPENAI_CODEX_IMPORT_LABEL = "Import Existing Codex Login";
-const OPENAI_CODEX_IMPORT_HINT = "Import an existing ~/.codex login";
-const OPENAI_CODEX_IMPORT_DETECTED_SUFFIX = "~/.codex detected";
const OPENAI_CODEX_DEVICE_PAIRING_LABEL = "OpenAI Codex Device Pairing";
const OPENAI_CODEX_DEVICE_PAIRING_HINT = "Pair in browser with a device code";
const OPENAI_CODEX_GPT_54_MODEL_ID = "gpt-5.4";
@@ -371,53 +362,6 @@ async function runOpenAICodexDeviceCode(ctx: ProviderAuthContext) {
}
}
-async function runImportOpenAICodexCliAuth(ctx: ProviderAuthContext) {
- const profile = readOpenAICodexCliOAuthProfile({
- env: ctx.env ?? process.env,
- store: ensureAuthProfileStoreForLocalUpdate(ctx.agentDir),
- });
- if (!profile) {
- const message =
- "No compatible ~/.codex ChatGPT login found. Use Browser Login or Device Pairing instead.";
- ctx.runtime.error(message);
- await ctx.prompter.note(message, OPENAI_CODEX_IMPORT_LABEL);
- return { profiles: [] };
- }
-
- return {
- profiles: [{ profileId: profile.profileId, credential: profile.credential }],
- configPatch: {
- agents: {
- defaults: {
- models: {
- [OPENAI_CODEX_DEFAULT_MODEL]: {},
- },
- },
- },
- },
- defaultModel: OPENAI_CODEX_DEFAULT_MODEL,
- notes: ["Imported existing Codex CLI login into OpenClaw canonical auth."],
- } satisfies ProviderAuthResult;
-}
-
-function ensureOpenAICodexCatalogAuthStore(ctx: { agentDir?: string; env?: NodeJS.ProcessEnv }) {
- const store = ensureAuthProfileStoreForLocalUpdate(ctx.agentDir);
- const profile = readOpenAICodexCliOAuthProfile({
- env: ctx.env ?? process.env,
- store,
- });
- if (!profile) {
- return store;
- }
- return {
- ...store,
- profiles: {
- ...store.profiles,
- [profile.profileId]: profile.credential,
- },
- };
-}
-
function buildOpenAICodexAuthDoctorHint(ctx: { profileId?: string }) {
if (ctx.profileId !== CODEX_CLI_PROFILE_ID) {
return undefined;
@@ -425,16 +369,7 @@ function buildOpenAICodexAuthDoctorHint(ctx: { profileId?: string }) {
return "Deprecated profile. Run `openclaw models auth login --provider openai-codex` or `openclaw configure`.";
}
-function buildOpenAICodexImportWizardLabel(hasCodexCliCredential: boolean) {
- if (!hasCodexCliCredential) {
- return OPENAI_CODEX_IMPORT_LABEL;
- }
- return `${OPENAI_CODEX_IMPORT_LABEL} (${OPENAI_CODEX_IMPORT_DETECTED_SUFFIX})`;
-}
-
export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
- const hasCodexCliCredential = hasOpenAICodexCliOAuthCredential();
- const importWizardLabel = buildOpenAICodexImportWizardLabel(hasCodexCliCredential);
return {
id: PROVIDER_ID,
label: "OpenAI Codex",
@@ -474,26 +409,11 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
}
},
},
- {
- id: "import-codex-cli",
- label: importWizardLabel,
- hint: OPENAI_CODEX_IMPORT_HINT,
- kind: "oauth",
- wizard: {
- choiceId: "openai-codex-import",
- choiceLabel: importWizardLabel,
- choiceHint: OPENAI_CODEX_IMPORT_HINT,
- assistantPriority: OPENAI_CODEX_IMPORT_ASSISTANT_PRIORITY,
- assistantVisibility: hasCodexCliCredential ? "visible" : "manual-only",
- ...OPENAI_WIZARD_GROUP,
- },
- run: async (ctx) => await runImportOpenAICodexCliAuth(ctx),
- },
],
catalog: {
order: "profile",
run: async (ctx) => {
- const authStore = ensureOpenAICodexCatalogAuthStore(ctx);
+ const authStore = ensureAuthProfileStoreForLocalUpdate(ctx.agentDir);
if (listProfilesForProvider(authStore, PROVIDER_ID).length === 0) {
return null;
}
@@ -546,13 +466,6 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
fetchUsageSnapshot: async (ctx) =>
await fetchCodexUsage(ctx.token, ctx.accountId, ctx.timeoutMs, ctx.fetchFn),
refreshOAuth: async (cred) => await refreshOpenAICodexOAuthCredential(cred),
- resolveExternalAuthProfiles: (ctx) => {
- const profile = readOpenAICodexCliOAuthProfile({
- env: ctx.env,
- store: ctx.store,
- });
- return profile ? [{ ...profile, persistence: "runtime-only" }] : [];
- },
augmentModelCatalog: (ctx) => {
const gpt54Template = findCatalogTemplate({
entries: ctx.entries,
diff --git a/extensions/openai/provider-contract-api.ts b/extensions/openai/provider-contract-api.ts
index ec492daf5af..3abdc46c186 100644
--- a/extensions/openai/provider-contract-api.ts
+++ b/extensions/openai/provider-contract-api.ts
@@ -41,20 +41,6 @@ export function createOpenAICodexProvider(): ProviderPlugin {
...OPENAI_WIZARD_GROUP,
},
},
- {
- id: "import-codex-cli",
- kind: "oauth",
- label: "Import Existing Codex Login",
- hint: "Import an existing ~/.codex login",
- run: noopAuth,
- wizard: {
- choiceId: "openai-codex-import",
- choiceLabel: "Import Existing Codex Login",
- choiceHint: "Import an existing ~/.codex login",
- assistantPriority: -20,
- ...OPENAI_WIZARD_GROUP,
- },
- },
],
};
}
diff --git a/src/commands/auth-choice-options.test.ts b/src/commands/auth-choice-options.test.ts
index 88734f3e9b4..d86c065574f 100644
--- a/src/commands/auth-choice-options.test.ts
+++ b/src/commands/auth-choice-options.test.ts
@@ -447,7 +447,7 @@ describe("buildAuthChoiceOptions", () => {
]);
});
- it("orders OpenAI auth methods as api key, login, import, then device pairing", () => {
+ it("orders OpenAI auth methods as api key, browser login, then device pairing", () => {
resolveProviderWizardOptions.mockReturnValue([
{
value: "openai-api-key",
@@ -463,13 +463,6 @@ describe("buildAuthChoiceOptions", () => {
groupLabel: "OpenAI",
assistantPriority: -30,
},
- {
- value: "openai-codex-import",
- label: "Import Existing Codex Login (~/.codex detected)",
- groupId: "openai",
- groupLabel: "OpenAI",
- assistantPriority: -20,
- },
{
value: "openai-codex-device-code",
label: "OpenAI Codex Device Pairing",
@@ -489,7 +482,6 @@ describe("buildAuthChoiceOptions", () => {
expect(openAIGroup?.options.map((option) => option.value)).toEqual([
"openai-api-key",
"openai-codex",
- "openai-codex-import",
"openai-codex-device-code",
]);
});