authorgravatar for yordis.prieto@gmail.comYordis Prieto <yordis.prieto@gmail.com> 2026-10-02 18:50:01-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-02 15:50:01-07:00
loga8927712f8b3338fa3c47a0c657b6e11d7e9aaec
tree4f3e8aed8d85cb5c6fd7da8cda33b0742006985d
parenta7b3ce8c0896d123a7c3f02c586ff8193841cc09
signature Signed by PGP key B5690EEEBB952194

fix(server): Codex auth tokens are an omitted key, never undefined (#14908)

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>

2 files changed, 26 insertions(+), 13 deletions(-)

apps/server/src/telemetry/Identify.test.ts+22-9
...@@ -57,7 +57,24 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {...@@ -57,7 +57,24 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {
57 ),57 ),
58 );58 );
5959
60 it.effect("falls back quietly when Codex authenticates with an API key", () => {60 it.effect.each([
61 {
62 login: "an API key",
63 secret: "sk-private-openai-api-key",
64 authJson: (secret: string) => `{"auth_mode":"apikey","OPENAI_API_KEY":"${secret}"}`,
65 },
66 {
67 login: "an agent identity",
68 secret: "private-agent-identity-jwt",
69 authJson: (secret: string) =>
70 `{"auth_mode":"agentIdentity","OPENAI_API_KEY":null,"agent_identity":"${secret}"}`,
71 },
72 {
73 login: "a personal access token",
74 secret: "private-personal-access-token",
75 authJson: (secret: string) => `{"OPENAI_API_KEY":null,"personal_access_token":"${secret}"}`,
76 },
77 ])("falls back quietly when Codex authenticates with $login", ({ secret, authJson }) => {
61 const logs: CapturedLog[] = [];78 const logs: CapturedLog[] = [];
62 const logger = makeCaptureLogger(logs);79 const logger = makeCaptureLogger(logs);
6380
...@@ -67,14 +84,10 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {...@@ -67,14 +84,10 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {
67 const path = yield* Path.Path;84 const path = yield* Path.Path;
68 const homeDirectory = path.join(config.baseDir, "home");85 const homeDirectory = path.join(config.baseDir, "home");
69 const codexAuthPath = path.join(homeDirectory, ".codex", "auth.json");86 const codexAuthPath = path.join(homeDirectory, ".codex", "auth.json");
70 const anonymousId = "api-key-fallback-anonymous-id";87 const anonymousId = "tokenless-codex-anonymous-id";
71 const privateApiKey = "sk-private-openai-api-key";
7288
73 yield* fileSystem.makeDirectory(path.dirname(codexAuthPath), { recursive: true });89 yield* fileSystem.makeDirectory(path.dirname(codexAuthPath), { recursive: true });
74 yield* fileSystem.writeFileString(90 yield* fileSystem.writeFileString(codexAuthPath, authJson(secret));
75 codexAuthPath,
76 `{"auth_mode":"apikey","OPENAI_API_KEY":"${privateApiKey}"}`,
77 );
78 yield* fileSystem.writeFileString(config.anonymousIdPath, anonymousId);91 yield* fileSystem.writeFileString(config.anonymousIdPath, anonymousId);
7992
80 const identifier = yield* Identify.getTelemetryIdentifierForHome(homeDirectory);93 const identifier = yield* Identify.getTelemetryIdentifierForHome(homeDirectory);
...@@ -87,12 +100,12 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {...@@ -87,12 +100,12 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {
87 [String(log.message), ...Object.values(log.annotations).map(String)].join("\n"),100 [String(log.message), ...Object.values(log.annotations).map(String)].join("\n"),
88 )101 )
89 .join("\n");102 .join("\n");
90 assert.notInclude(allLogs, privateApiKey);103 assert.notInclude(allLogs, secret);
91 }).pipe(104 }).pipe(
92 Effect.provide(105 Effect.provide(
93 Layer.merge(106 Layer.merge(
94 ServerConfig.layerTest(process.cwd(), {107 ServerConfig.layerTest(process.cwd(), {
95 prefix: "t3-telemetry-identify-apikey-",108 prefix: "t3-telemetry-identify-tokenless-",
96 }),109 }),
97 Logger.layer([logger], { mergeWithExisting: false }),110 Logger.layer([logger], { mergeWithExisting: false }),
98 ),111 ),
apps/server/src/telemetry/Identify.ts+4-4
...@@ -11,12 +11,12 @@ import * as Schema from "effect/Schema";...@@ -11,12 +11,12 @@ import * as Schema from "effect/Schema";
11import * as ServerConfig from "../config.ts";11import * as ServerConfig from "../config.ts";
1212
13/**13/**
14 * Codex omits `tokens` entirely when the install authenticates with an API key14 * Codex writes `tokens` only for ChatGPT logins and omits the key for API-key,
15 * rather than a ChatGPT account, so an absent `tokens` is a supported install15 * agent-identity, and personal-access-token logins, so its absence is a
16 * and not a malformed file.16 * supported install and not a malformed file.
17 */17 */
18const CodexAuthJsonSchema = Schema.Struct({18const CodexAuthJsonSchema = Schema.Struct({
19 tokens: Schema.optional(19 tokens: Schema.optionalKey(
20 Schema.Struct({20 Schema.Struct({
21 account_id: Schema.String,21 account_id: Schema.String,
22 }),22 }),