authorgravatar for yordis.prieto@gmail.comYordis Prieto <yordis.prieto@gmail.com> 2026-10-02 17:45:42-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-02 14:45:42-07:00
logbf7121d7a25fe84d76561c3142b9433615a703a1
tree5c90bf94789cddb59f200fa4a7455880f6fb95d8
parentb4d3d51ac99d4306d754afb5c78c49845bfac3c1
signature Signed by PGP key B5690EEEBB952194

fix(server): API-key Codex installs no longer warn on every start (#14903)

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

2 files changed, 59 insertions(+), 4 deletions(-)

apps/server/src/telemetry/Identify.test.ts+43
......@@ -57,6 +57,49 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {
5757 ),
5858 );
5959
60 it.effect("falls back quietly when Codex authenticates with an API key", () => {
61 const logs: CapturedLog[] = [];
62 const logger = makeCaptureLogger(logs);
63
64 return Effect.gen(function* () {
65 const config = yield* ServerConfig.ServerConfig;
66 const fileSystem = yield* FileSystem.FileSystem;
67 const path = yield* Path.Path;
68 const homeDirectory = path.join(config.baseDir, "home");
69 const codexAuthPath = path.join(homeDirectory, ".codex", "auth.json");
70 const anonymousId = "api-key-fallback-anonymous-id";
71 const privateApiKey = "sk-private-openai-api-key";
72
73 yield* fileSystem.makeDirectory(path.dirname(codexAuthPath), { recursive: true });
74 yield* fileSystem.writeFileString(
75 codexAuthPath,
76 `{"auth_mode":"apikey","OPENAI_API_KEY":"${privateApiKey}"}`,
77 );
78 yield* fileSystem.writeFileString(config.anonymousIdPath, anonymousId);
79
80 const identifier = yield* Identify.getTelemetryIdentifierForHome(homeDirectory);
81
82 assert.equal(identifier, sha256(anonymousId));
83 assert.isUndefined(findIdentityLog(logs, "codex", "TelemetryIdentityDecodeError"));
84 assert.isUndefined(findIdentityLog(logs, "codex", "TelemetryIdentityReadError"));
85 const allLogs = logs
86 .map((log) =>
87 [String(log.message), ...Object.values(log.annotations).map(String)].join("\n"),
88 )
89 .join("\n");
90 assert.notInclude(allLogs, privateApiKey);
91 }).pipe(
92 Effect.provide(
93 Layer.merge(
94 ServerConfig.layerTest(process.cwd(), {
95 prefix: "t3-telemetry-identify-apikey-",
96 }),
97 Logger.layer([logger], { mergeWithExisting: false }),
98 ),
99 ),
100 );
101 });
102
60103 it.effect("logs structured decode context and falls back from malformed Codex auth", () => {
61104 const logs: CapturedLog[] = [];
62105 const logger = makeCaptureLogger(logs);
apps/server/src/telemetry/Identify.ts+16-4
......@@ -10,10 +10,17 @@ import * as Schema from "effect/Schema";
1010
1111import * as ServerConfig from "../config.ts";
1212
13/**
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.
17 */
1318const CodexAuthJsonSchema = Schema.Struct({
14 tokens: Schema.Struct({
15 account_id: Schema.String,
16 }),
19 tokens: Schema.optional(
20 Schema.Struct({
21 account_id: Schema.String,
22 }),
23 ),
1724});
1825
1926const ClaudeJsonSchema = Schema.Struct({
......@@ -183,7 +190,9 @@ const getCodexAccountId = Effect.fn("TelemetryIdentity.getCodexAccountId")(funct
183190 ),
184191 );
185192
186 return Option.some(authJson.tokens.account_id);
193 return authJson.tokens === undefined
194 ? Option.none<string>()
195 : Option.some(authJson.tokens.account_id);
187196});
188197
189198const getClaudeUserId = Effect.fn("TelemetryIdentity.getClaudeUserId")(function* (
......@@ -250,6 +259,9 @@ const upsertAnonymousId = Effect.gen(function* () {
250259 * 1. ~/.codex/auth.json tokens.account_id
251260 * 2. ~/.claude.json userID
252261 * 3. ~/.t3/telemetry/anonymous-id
262 *
263 * A missing file or an API-key-only Codex auth.json falls through quietly. Only
264 * unreadable or malformed files warn.
253265 */
254266export const getTelemetryIdentifierForHome = Effect.fn("getTelemetryIdentifierForHome")(
255267 function* (homeDirectory: string) {