Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions apps/server/src/telemetry/Identify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,49 @@ it.layer(NodeServices.layer)("telemetry identity", (it) => {
),
);

it.effect("falls back quietly when Codex authenticates with an API key", () => {
const logs: CapturedLog[] = [];
const logger = makeCaptureLogger(logs);

return Effect.gen(function* () {
const config = yield* ServerConfig.ServerConfig;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const homeDirectory = path.join(config.baseDir, "home");
const codexAuthPath = path.join(homeDirectory, ".codex", "auth.json");
const anonymousId = "api-key-fallback-anonymous-id";
const privateApiKey = "sk-private-openai-api-key";

yield* fileSystem.makeDirectory(path.dirname(codexAuthPath), { recursive: true });
yield* fileSystem.writeFileString(
codexAuthPath,
`{"auth_mode":"apikey","OPENAI_API_KEY":"${privateApiKey}"}`,
);
yield* fileSystem.writeFileString(config.anonymousIdPath, anonymousId);

const identifier = yield* Identify.getTelemetryIdentifierForHome(homeDirectory);

assert.equal(identifier, sha256(anonymousId));
assert.isUndefined(findIdentityLog(logs, "codex", "TelemetryIdentityDecodeError"));
assert.isUndefined(findIdentityLog(logs, "codex", "TelemetryIdentityReadError"));
const allLogs = logs
.map((log) =>
[String(log.message), ...Object.values(log.annotations).map(String)].join("\n"),
)
.join("\n");
assert.notInclude(allLogs, privateApiKey);
}).pipe(
Effect.provide(
Layer.merge(
ServerConfig.layerTest(process.cwd(), {
prefix: "t3-telemetry-identify-apikey-",
}),
Logger.layer([logger], { mergeWithExisting: false }),
),
),
);
});

it.effect("logs structured decode context and falls back from malformed Codex auth", () => {
const logs: CapturedLog[] = [];
const logger = makeCaptureLogger(logs);
Expand Down
20 changes: 16 additions & 4 deletions apps/server/src/telemetry/Identify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ import * as Schema from "effect/Schema";

import * as ServerConfig from "../config.ts";

/**
* Codex omits `tokens` entirely when the install authenticates with an API key
* rather than a ChatGPT account, so an absent `tokens` is a supported install
* and not a malformed file.
*/
const CodexAuthJsonSchema = Schema.Struct({
tokens: Schema.Struct({
account_id: Schema.String,
}),
tokens: Schema.optional(
Schema.Struct({
account_id: Schema.String,
}),
),
});

const ClaudeJsonSchema = Schema.Struct({
Expand Down Expand Up @@ -183,7 +190,9 @@ const getCodexAccountId = Effect.fn("TelemetryIdentity.getCodexAccountId")(funct
),
);

return Option.some(authJson.tokens.account_id);
return authJson.tokens === undefined
? Option.none<string>()
: Option.some(authJson.tokens.account_id);
});

const getClaudeUserId = Effect.fn("TelemetryIdentity.getClaudeUserId")(function* (
Expand Down Expand Up @@ -250,6 +259,9 @@ const upsertAnonymousId = Effect.gen(function* () {
* 1. ~/.codex/auth.json tokens.account_id
* 2. ~/.claude.json userID
* 3. ~/.t3/telemetry/anonymous-id
*
* A missing file or an API-key-only Codex auth.json falls through quietly. Only
* unreadable or malformed files warn.
*/
export const getTelemetryIdentifierForHome = Effect.fn("getTelemetryIdentifierForHome")(
function* (homeDirectory: string) {
Expand Down
27 changes: 27 additions & 0 deletions docs/fork/0007-codex-api-key-auth-is-supported.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 0007: API-key Codex installs are not reported as broken

- PR: [TrogonStack/t3code#15](https://github.com/TrogonStack/t3code/pull/15)
- Status: active

## What you can do now

- Run Codex authenticated with an API key instead of a ChatGPT account
without a warning and a stack trace on every server start. Telemetry falls
back to the anonymous identifier quietly, the same way it already did when
there is no Codex auth file at all.
- An auth file that really is unreadable or corrupt still warns, so the
warning keeps meaning something.

## Why

API keys are a supported way to authenticate Codex, so an install using one
is healthy and should not read as a malfunction in the logs. A warning that
fires on every start of a working setup is worse than no warning, because it
teaches you to scroll past the one that matters.

## Upstream considerations

Nothing here is fork-specific, so this belongs upstream as an ordinary bug
fix. Submit it, then delete this entry once it merges. The surface is small
enough that carrying it in the meantime costs nothing at sync time, though it
does sit in a shared file, so a sync must not drop it.
7 changes: 4 additions & 3 deletions docs/fork/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Each entry uses these sections:

## Ledger

| # | Divergence | PR | Status |
| ---- | ------------------------------------------------------------------------------------- | -------------------------------------------------- | ------ |
| 0003 | [Native subagent threads for Claude orchestrators](./0003-native-subagent-threads.md) | [#3](https://github.com/TrogonStack/t3code/pull/3) | active |
| # | Divergence | PR | Status |
| ---- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------ |
| 0003 | [Native subagent threads for Claude orchestrators](./0003-native-subagent-threads.md) | [#3](https://github.com/TrogonStack/t3code/pull/3) | active |
| 0007 | [API-key Codex installs are not reported as broken](./0007-codex-api-key-auth-is-supported.md) | [#15](https://github.com/TrogonStack/t3code/pull/15) | active |
Loading