authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-20 11:43:39-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-20 11:43:39-07:00
logef2569196f164df1096ebd8c614fafa0ebc43be9
tree2ee90805e63d9649f8cafa958804e19bfe8c84c8
parent34cef6202b6c2eb0594df0eca412b36daf819ba2
signature Signed by PGP key B5690EEEBB952194

Prefer Claude user ID for telemetry identity (#1249)


1 files changed, 24 insertions(+), 1 deletions(-)

apps/server/src/telemetry/Identify.ts+24-1
...@@ -9,6 +9,10 @@ const CodexAuthJsonSchema = Schema.Struct({...@@ -9,6 +9,10 @@ const CodexAuthJsonSchema = Schema.Struct({
9 }),9 }),
10});10});
1111
12const ClaudeJsonSchema = Schema.Struct({
13 userID: Schema.String,
14});
15
12class IdentifyUserError extends Schema.TaggedErrorClass<IdentifyUserError>()("IdentifyUserError", {16class IdentifyUserError extends Schema.TaggedErrorClass<IdentifyUserError>()("IdentifyUserError", {
13 message: Schema.String,17 message: Schema.String,
14 cause: Schema.optional(Schema.Defect),18 cause: Schema.optional(Schema.Defect),
...@@ -37,6 +41,19 @@ const getCodexAccountId = Effect.gen(function* () {...@@ -37,6 +41,19 @@ const getCodexAccountId = Effect.gen(function* () {
37 return authJson.tokens.account_id;41 return authJson.tokens.account_id;
38});42});
3943
44const getClaudeUserId = Effect.gen(function* () {
45 const fileSystem = yield* FileSystem.FileSystem;
46 const path = yield* Path.Path;
47
48 const claudeJsonPath = path.join(homedir(), ".claude.json");
49 const claudeJson = yield* Effect.flatMap(
50 fileSystem.readFileString(claudeJsonPath),
51 Schema.decodeEffect(Schema.fromJsonString(ClaudeJsonSchema)),
52 );
53
54 return claudeJson.userID;
55});
56
40const upsertAnonymousId = Effect.gen(function* () {57const upsertAnonymousId = Effect.gen(function* () {
41 const fileSystem = yield* FileSystem.FileSystem;58 const fileSystem = yield* FileSystem.FileSystem;
42 const path = yield* Path.Path;59 const path = yield* Path.Path;
...@@ -59,7 +76,8 @@ const upsertAnonymousId = Effect.gen(function* () {...@@ -59,7 +76,8 @@ const upsertAnonymousId = Effect.gen(function* () {
59/**76/**
60 * getTelemetryIdentifier - Users are "identified" by finding the first match of the following, then hashing the value.77 * getTelemetryIdentifier - Users are "identified" by finding the first match of the following, then hashing the value.
61 * 1. ~/.codex/auth.json tokens.account_id78 * 1. ~/.codex/auth.json tokens.account_id
62 * 2. ~/.t3/telemetry/anonymous-id79 * 2. ~/.claude.json userID
80 * 3. ~/.t3/telemetry/anonymous-id
63 */81 */
64export const getTelemetryIdentifier = Effect.gen(function* () {82export const getTelemetryIdentifier = Effect.gen(function* () {
65 const codexAccountId = yield* Effect.result(getCodexAccountId);83 const codexAccountId = yield* Effect.result(getCodexAccountId);
...@@ -67,6 +85,11 @@ export const getTelemetryIdentifier = Effect.gen(function* () {...@@ -67,6 +85,11 @@ export const getTelemetryIdentifier = Effect.gen(function* () {
67 return yield* hash(codexAccountId.success);85 return yield* hash(codexAccountId.success);
68 }86 }
6987
88 const claudeUserId = yield* Effect.result(getClaudeUserId);
89 if (claudeUserId._tag === "Success") {
90 return yield* hash(claudeUserId.success);
91 }
92
70 const anonymousId = yield* Effect.result(upsertAnonymousId);93 const anonymousId = yield* Effect.result(upsertAnonymousId);
71 if (anonymousId._tag === "Success") {94 if (anonymousId._tag === "Success") {
72 return yield* hash(anonymousId.success);95 return yield* hash(anonymousId.success);