diff --git a/apps/server/src/telemetry/Identify.ts b/apps/server/src/telemetry/Identify.ts index cf6ac72178..e63b0ec228 100644 --- a/apps/server/src/telemetry/Identify.ts +++ b/apps/server/src/telemetry/Identify.ts @@ -9,6 +9,10 @@ const CodexAuthJsonSchema = Schema.Struct({ }), }); +const ClaudeJsonSchema = Schema.Struct({ + userID: Schema.String, +}); + class IdentifyUserError extends Schema.TaggedErrorClass()("IdentifyUserError", { message: Schema.String, cause: Schema.optional(Schema.Defect), @@ -37,6 +41,19 @@ const getCodexAccountId = Effect.gen(function* () { return authJson.tokens.account_id; }); +const getClaudeUserId = Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + + const claudeJsonPath = path.join(homedir(), ".claude.json"); + const claudeJson = yield* Effect.flatMap( + fileSystem.readFileString(claudeJsonPath), + Schema.decodeEffect(Schema.fromJsonString(ClaudeJsonSchema)), + ); + + return claudeJson.userID; +}); + const upsertAnonymousId = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; @@ -59,7 +76,8 @@ const upsertAnonymousId = Effect.gen(function* () { /** * getTelemetryIdentifier - Users are "identified" by finding the first match of the following, then hashing the value. * 1. ~/.codex/auth.json tokens.account_id - * 2. ~/.t3/telemetry/anonymous-id + * 2. ~/.claude.json userID + * 3. ~/.t3/telemetry/anonymous-id */ export const getTelemetryIdentifier = Effect.gen(function* () { const codexAccountId = yield* Effect.result(getCodexAccountId); @@ -67,6 +85,11 @@ export const getTelemetryIdentifier = Effect.gen(function* () { return yield* hash(codexAccountId.success); } + const claudeUserId = yield* Effect.result(getClaudeUserId); + if (claudeUserId._tag === "Success") { + return yield* hash(claudeUserId.success); + } + const anonymousId = yield* Effect.result(upsertAnonymousId); if (anonymousId._tag === "Success") { return yield* hash(anonymousId.success);