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) => {
5757 ),
5858 );
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 }) => {
6178 const logs: CapturedLog[] = [];
6279 const logger = makeCaptureLogger(logs);
6380
......@@ -67,14 +84,10 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {
6784 const path = yield* Path.Path;
6885 const homeDirectory = path.join(config.baseDir, "home");
6986 const codexAuthPath = path.join(homeDirectory, ".codex", "auth.json");
70 const anonymousId = "api-key-fallback-anonymous-id";
71 const privateApiKey = "sk-private-openai-api-key";
87 const anonymousId = "tokenless-codex-anonymous-id";
7288
7389 yield* fileSystem.makeDirectory(path.dirname(codexAuthPath), { recursive: true });
74 yield* fileSystem.writeFileString(
75 codexAuthPath,
76 `{"auth_mode":"apikey","OPENAI_API_KEY":"${privateApiKey}"}`,
77 );
90 yield* fileSystem.writeFileString(codexAuthPath, authJson(secret));
7891 yield* fileSystem.writeFileString(config.anonymousIdPath, anonymousId);
7992
8093 const identifier = yield* Identify.getTelemetryIdentifierForHome(homeDirectory);
......@@ -87,12 +100,12 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {
87100 [String(log.message), ...Object.values(log.annotations).map(String)].join("\n"),
88101 )
89102 .join("\n");
90 assert.notInclude(allLogs, privateApiKey);
103 assert.notInclude(allLogs, secret);
91104 }).pipe(
92105 Effect.provide(
93106 Layer.merge(
94107 ServerConfig.layerTest(process.cwd(), {
95 prefix: "t3-telemetry-identify-apikey-",
108 prefix: "t3-telemetry-identify-tokenless-",
96109 }),
97110 Logger.layer([logger], { mergeWithExisting: false }),
98111 ),
apps/server/src/telemetry/Identify.ts+4-4
......@@ -11,12 +11,12 @@ import * as Schema from "effect/Schema";
1111import * as ServerConfig from "../config.ts";
1212
1313/**
14 * Codex omits `tokens` entirely when the install authenticates with an API key
15 * rather than a ChatGPT account, so an absent `tokens` is a supported install
16 * and not a malformed file.
14 * Codex writes `tokens` only for ChatGPT logins and omits the key for API-key,
15 * agent-identity, and personal-access-token logins, so its absence is a
16 * supported install and not a malformed file.
1717 */
1818const CodexAuthJsonSchema = Schema.Struct({
19 tokens: Schema.optional(
19 tokens: Schema.optionalKey(
2020 Schema.Struct({
2121 account_id: Schema.String,
2222 }),