From a9dcea023bc5e6f30956d761675fde24e13def0f Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 24 Mar 2026 09:47:48 -0700 Subject: [PATCH 1/7] fix(codex): check native auth before warning about missing API key The environment test warned about OPENAI_API_KEY being unset even when Codex was authenticated via `codex auth`. Now checks ~/.codex/auth.json before emitting the warning. Co-Authored-By: Claude Opus 4.6 --- .../adapters/codex-local/src/server/test.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/adapters/codex-local/src/server/test.ts b/packages/adapters/codex-local/src/server/test.ts index 292e53ee..fbf95e62 100644 --- a/packages/adapters/codex-local/src/server/test.ts +++ b/packages/adapters/codex-local/src/server/test.ts @@ -15,6 +15,7 @@ import { } from "@paperclipai/adapter-utils/server-utils"; import path from "node:path"; import { parseCodexJsonl } from "./parse.js"; +import { readCodexAuthInfo } from "./quota.js"; function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { if (checks.some((check) => check.level === "error")) return "fail"; @@ -108,12 +109,22 @@ export async function testEnvironment( detail: `Detected in ${source}.`, }); } else { - checks.push({ - code: "codex_openai_api_key_missing", - level: "warn", - message: "OPENAI_API_KEY is not set. Codex runs may fail until authentication is configured.", - hint: "Set OPENAI_API_KEY in adapter env, shell environment, or Codex auth configuration.", - }); + const codexAuth = await readCodexAuthInfo().catch(() => null); + if (codexAuth) { + checks.push({ + code: "codex_native_auth_present", + level: "info", + message: "Codex is authenticated via its own auth configuration.", + detail: codexAuth.email ? `Logged in as ${codexAuth.email}.` : "Credentials found in ~/.codex/auth.json.", + }); + } else { + checks.push({ + code: "codex_openai_api_key_missing", + level: "warn", + message: "OPENAI_API_KEY is not set. Codex runs may fail until authentication is configured.", + hint: "Set OPENAI_API_KEY in adapter env, shell environment, or run `codex auth` to log in.", + }); + } } const canRunProbe = From 06b85d62b2ceaa961e914681e0b27e1b94df934c Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 24 Mar 2026 09:51:44 -0700 Subject: [PATCH 2/7] test(codex): add coverage for native auth detection in environment probe Add tests for codex_native_auth_present and codex_openai_api_key_missing code paths. Also pass adapter-configured CODEX_HOME through to readCodexAuthInfo so the probe respects per-adapter home directories. Co-Authored-By: Claude Opus 4.6 --- .../adapters/codex-local/src/server/quota.ts | 4 +- .../adapters/codex-local/src/server/test.ts | 3 +- .../codex-local-adapter-environment.test.ts | 61 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/packages/adapters/codex-local/src/server/quota.ts b/packages/adapters/codex-local/src/server/quota.ts index b51f6646..7bc771e4 100644 --- a/packages/adapters/codex-local/src/server/quota.ts +++ b/packages/adapters/codex-local/src/server/quota.ts @@ -107,8 +107,8 @@ function parsePlanAndEmailFromToken(idToken: string | null, accessToken: string return { email: null, planType: null }; } -export async function readCodexAuthInfo(): Promise { - const authPath = path.join(codexHomeDir(), "auth.json"); +export async function readCodexAuthInfo(codexHome?: string): Promise { + const authPath = path.join(codexHome ?? codexHomeDir(), "auth.json"); let raw: string; try { raw = await fs.readFile(authPath, "utf8"); diff --git a/packages/adapters/codex-local/src/server/test.ts b/packages/adapters/codex-local/src/server/test.ts index fbf95e62..ed11cd6a 100644 --- a/packages/adapters/codex-local/src/server/test.ts +++ b/packages/adapters/codex-local/src/server/test.ts @@ -109,7 +109,8 @@ export async function testEnvironment( detail: `Detected in ${source}.`, }); } else { - const codexAuth = await readCodexAuthInfo().catch(() => null); + const codexHome = isNonEmpty(env.CODEX_HOME) ? env.CODEX_HOME : undefined; + const codexAuth = await readCodexAuthInfo(codexHome).catch(() => null); if (codexAuth) { checks.push({ code: "codex_native_auth_present", diff --git a/server/src/__tests__/codex-local-adapter-environment.test.ts b/server/src/__tests__/codex-local-adapter-environment.test.ts index a9201c98..6c77d30f 100644 --- a/server/src/__tests__/codex-local-adapter-environment.test.ts +++ b/server/src/__tests__/codex-local-adapter-environment.test.ts @@ -32,6 +32,67 @@ describe("codex_local environment diagnostics", () => { await fs.rm(path.dirname(cwd), { recursive: true, force: true }); }); + it("emits codex_native_auth_present when ~/.codex/auth.json exists and OPENAI_API_KEY is unset", async () => { + const root = path.join( + os.tmpdir(), + `paperclip-codex-auth-${Date.now()}-${Math.random().toString(16).slice(2)}`, + ); + const codexHome = path.join(root, ".codex"); + const cwd = path.join(root, "workspace"); + + try { + await fs.mkdir(codexHome, { recursive: true }); + await fs.writeFile( + path.join(codexHome, "auth.json"), + JSON.stringify({ accessToken: "fake-token", accountId: "acct-1" }), + ); + + const result = await testEnvironment({ + companyId: "company-1", + adapterType: "codex_local", + config: { + command: process.execPath, + cwd, + env: { CODEX_HOME: codexHome }, + }, + }); + + expect(result.checks.some((check) => check.code === "codex_native_auth_present")).toBe(true); + expect(result.checks.some((check) => check.code === "codex_openai_api_key_missing")).toBe(false); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } + }); + + it("emits codex_openai_api_key_missing when neither env var nor native auth exists", async () => { + const root = path.join( + os.tmpdir(), + `paperclip-codex-noauth-${Date.now()}-${Math.random().toString(16).slice(2)}`, + ); + const codexHome = path.join(root, ".codex"); + const cwd = path.join(root, "workspace"); + + try { + await fs.mkdir(codexHome, { recursive: true }); + // No auth.json written + + const result = await testEnvironment({ + companyId: "company-1", + adapterType: "codex_local", + config: { + command: process.execPath, + cwd, + env: { CODEX_HOME: codexHome }, + }, + }); + + expect(result.checks.some((check) => check.code === "codex_openai_api_key_missing")).toBe(true); + expect(result.checks.some((check) => check.code === "codex_native_auth_present")).toBe(false); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } + }); + itWindows("runs the hello probe when Codex is available via a Windows .cmd wrapper", async () => { const root = path.join( os.tmpdir(), From 58c511af9a1a57303df20b22c08ca9d750898dc4 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 24 Mar 2026 09:58:40 -0700 Subject: [PATCH 3/7] test(codex): isolate auth tests from host OPENAI_API_KEY Use vi.stubEnv to clear OPENAI_API_KEY in both new tests so they don't silently pass the wrong branch when the key is set in the test runner's environment. Co-Authored-By: Claude Opus 4.6 --- .../src/__tests__/codex-local-adapter-environment.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/codex-local-adapter-environment.test.ts b/server/src/__tests__/codex-local-adapter-environment.test.ts index 6c77d30f..5cb3ae5e 100644 --- a/server/src/__tests__/codex-local-adapter-environment.test.ts +++ b/server/src/__tests__/codex-local-adapter-environment.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -7,6 +7,9 @@ import { testEnvironment } from "@paperclipai/adapter-codex-local/server"; const itWindows = process.platform === "win32" ? it : it.skip; describe("codex_local environment diagnostics", () => { + afterEach(() => { + vi.unstubAllEnvs(); + }); it("creates a missing working directory when cwd is absolute", async () => { const cwd = path.join( os.tmpdir(), @@ -33,6 +36,7 @@ describe("codex_local environment diagnostics", () => { }); it("emits codex_native_auth_present when ~/.codex/auth.json exists and OPENAI_API_KEY is unset", async () => { + vi.stubEnv("OPENAI_API_KEY", ""); const root = path.join( os.tmpdir(), `paperclip-codex-auth-${Date.now()}-${Math.random().toString(16).slice(2)}`, @@ -65,6 +69,7 @@ describe("codex_local environment diagnostics", () => { }); it("emits codex_openai_api_key_missing when neither env var nor native auth exists", async () => { + vi.stubEnv("OPENAI_API_KEY", ""); const root = path.join( os.tmpdir(), `paperclip-codex-noauth-${Date.now()}-${Math.random().toString(16).slice(2)}`, From 0ce4134ce16031956ac1e4482c0ebd4acdc86115 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 24 Mar 2026 10:54:05 -0700 Subject: [PATCH 4/7] fix(codex): use actual CODEX_HOME in auth detail message Show the configured CODEX_HOME path instead of hardcoded ~/.codex when the email fallback message is displayed. Co-Authored-By: Claude Opus 4.6 --- packages/adapters/codex-local/src/server/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adapters/codex-local/src/server/test.ts b/packages/adapters/codex-local/src/server/test.ts index ed11cd6a..471d6cb1 100644 --- a/packages/adapters/codex-local/src/server/test.ts +++ b/packages/adapters/codex-local/src/server/test.ts @@ -116,7 +116,7 @@ export async function testEnvironment( code: "codex_native_auth_present", level: "info", message: "Codex is authenticated via its own auth configuration.", - detail: codexAuth.email ? `Logged in as ${codexAuth.email}.` : "Credentials found in ~/.codex/auth.json.", + detail: codexAuth.email ? `Logged in as ${codexAuth.email}.` : `Credentials found in ${codexHome ?? "~/.codex"}/auth.json.`, }); } else { checks.push({ From 4da83296a9d43839e118e8a173c2bbbd1671f28b Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 24 Mar 2026 12:33:11 -0700 Subject: [PATCH 5/7] test(codex): move OPENAI_API_KEY stub to beforeEach for all tests Consolidate the env stub into beforeEach so the pre-existing cwd test is also isolated from host OPENAI_API_KEY, avoiding non-deterministic filesystem side effects. Co-Authored-By: Claude Opus 4.6 --- .../src/__tests__/codex-local-adapter-environment.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/__tests__/codex-local-adapter-environment.test.ts b/server/src/__tests__/codex-local-adapter-environment.test.ts index 5cb3ae5e..ba92a224 100644 --- a/server/src/__tests__/codex-local-adapter-environment.test.ts +++ b/server/src/__tests__/codex-local-adapter-environment.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -7,6 +7,9 @@ import { testEnvironment } from "@paperclipai/adapter-codex-local/server"; const itWindows = process.platform === "win32" ? it : it.skip; describe("codex_local environment diagnostics", () => { + beforeEach(() => { + vi.stubEnv("OPENAI_API_KEY", ""); + }); afterEach(() => { vi.unstubAllEnvs(); }); @@ -36,7 +39,6 @@ describe("codex_local environment diagnostics", () => { }); it("emits codex_native_auth_present when ~/.codex/auth.json exists and OPENAI_API_KEY is unset", async () => { - vi.stubEnv("OPENAI_API_KEY", ""); const root = path.join( os.tmpdir(), `paperclip-codex-auth-${Date.now()}-${Math.random().toString(16).slice(2)}`, @@ -69,7 +71,6 @@ describe("codex_local environment diagnostics", () => { }); it("emits codex_openai_api_key_missing when neither env var nor native auth exists", async () => { - vi.stubEnv("OPENAI_API_KEY", ""); const root = path.join( os.tmpdir(), `paperclip-codex-noauth-${Date.now()}-${Math.random().toString(16).slice(2)}`, From 4eecd23ea3ed4f61e1c8c419d7dcc2b4f2ea2a3d Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 24 Mar 2026 12:46:16 -0700 Subject: [PATCH 6/7] fix(codex): use codexHomeDir() fallback for accurate auth detail path When adapter config has no CODEX_HOME but process.env.CODEX_HOME is set, readCodexAuthInfo reads from the process env path. The detail message now uses codexHomeDir() instead of hardcoded "~/.codex" so the displayed path always matches where credentials were read from. Co-Authored-By: Claude Opus 4.6 --- packages/adapters/codex-local/src/server/test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/adapters/codex-local/src/server/test.ts b/packages/adapters/codex-local/src/server/test.ts index 471d6cb1..cf5a183e 100644 --- a/packages/adapters/codex-local/src/server/test.ts +++ b/packages/adapters/codex-local/src/server/test.ts @@ -15,7 +15,7 @@ import { } from "@paperclipai/adapter-utils/server-utils"; import path from "node:path"; import { parseCodexJsonl } from "./parse.js"; -import { readCodexAuthInfo } from "./quota.js"; +import { codexHomeDir, readCodexAuthInfo } from "./quota.js"; function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { if (checks.some((check) => check.level === "error")) return "fail"; @@ -116,7 +116,7 @@ export async function testEnvironment( code: "codex_native_auth_present", level: "info", message: "Codex is authenticated via its own auth configuration.", - detail: codexAuth.email ? `Logged in as ${codexAuth.email}.` : `Credentials found in ${codexHome ?? "~/.codex"}/auth.json.`, + detail: codexAuth.email ? `Logged in as ${codexAuth.email}.` : `Credentials found in ${codexHome ?? codexHomeDir()}/auth.json.`, }); } else { checks.push({ From 1696ff0c3fd304331ac55b4dcaa555acdf9a8590 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 24 Mar 2026 15:27:32 -0700 Subject: [PATCH 7/7] fix(codex): use path.join for auth detail message path Use path.join instead of string concatenation for the auth.json fallback path in the detail message, ensuring correct path separators on Windows. Co-Authored-By: Claude Opus 4.6 --- packages/adapters/codex-local/src/server/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adapters/codex-local/src/server/test.ts b/packages/adapters/codex-local/src/server/test.ts index cf5a183e..64af601b 100644 --- a/packages/adapters/codex-local/src/server/test.ts +++ b/packages/adapters/codex-local/src/server/test.ts @@ -116,7 +116,7 @@ export async function testEnvironment( code: "codex_native_auth_present", level: "info", message: "Codex is authenticated via its own auth configuration.", - detail: codexAuth.email ? `Logged in as ${codexAuth.email}.` : `Credentials found in ${codexHome ?? codexHomeDir()}/auth.json.`, + detail: codexAuth.email ? `Logged in as ${codexAuth.email}.` : `Credentials found in ${path.join(codexHome ?? codexHomeDir(), "auth.json")}.`, }); } else { checks.push({