Skip to content

Commit 04abbdc

Browse files
gewenyu99claude
andcommitted
feat(analytics): identify the user so feature flags can target by email
identifyUser sets the distinct id and person properties (email, name) before flag evaluation, and getFeatureFlag/getAllFlagsForWizard now send them. Previously the wizard sent only $app_name, so email-targeted flags never matched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f181850 commit 04abbdc

2 files changed

Lines changed: 30 additions & 4 deletions

File tree

src/lib/agent/agent-runner.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ async function bootstrapProgram(
321321
getUI().setRoleAtOrganization(roleAtOrganization);
322322
getUI().setApiUser(user);
323323

324+
// Identify the user (email, name) before evaluating flags, so flags can target
325+
// the individual user and not just $app_name.
326+
if (user) analytics.identifyUser(user);
324327
analytics.setGroups(groupsFromUser(user, host));
325328

326329
// Feature flags, variant metadata, and MCP url. Both arms need these, and the

src/utils/analytics.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class Analytics {
5555
private appName = 'wizard';
5656
private activeFlags: Record<string, string> | null = null;
5757
private groups: Record<string, string> = {};
58+
private personProperties: Record<string, string> = {};
5859

5960
constructor() {
6061
this.client = new PostHog(ANALYTICS_POSTHOG_PUBLIC_PROJECT_WRITE_KEY, {
@@ -85,6 +86,30 @@ export class Analytics {
8586
});
8687
}
8788

89+
/**
90+
* Identify the authenticated user. Sets the distinct id and records their
91+
* person properties (email, name) so events carry them and feature flags can
92+
* target the individual user. Without the email here, the wizard only sends
93+
* `$app_name`, so flags targeted by email never match.
94+
*/
95+
identifyUser(user: ApiUser) {
96+
this.setDistinctId(user.distinct_id);
97+
const props: Record<string, string> = {};
98+
if (user.email) props.email = user.email;
99+
const name = [user.first_name, user.last_name]
100+
.filter(Boolean)
101+
.join(' ')
102+
.trim();
103+
if (name) props.name = name;
104+
this.personProperties = props;
105+
this.client.identify({ distinctId: user.distinct_id, properties: props });
106+
}
107+
108+
/** Person properties sent with flag evaluation: app name plus the user's. */
109+
private flagPersonProperties(): Record<string, string> {
110+
return { $app_name: this.appName, ...this.personProperties };
111+
}
112+
88113
setTag(key: string, value: string | boolean | number | null | undefined) {
89114
this.tags[key] = value;
90115
}
@@ -125,9 +150,7 @@ export class Analytics {
125150
const distinctId = this.distinctId ?? this.anonymousId;
126151
return await this.client.getFeatureFlag(flagKey, distinctId, {
127152
sendFeatureFlagEvents: true,
128-
personProperties: {
129-
$app_name: this.appName,
130-
},
153+
personProperties: this.flagPersonProperties(),
131154
});
132155
} catch (error) {
133156
debug('Failed to get feature flag:', flagKey, error);
@@ -147,7 +170,7 @@ export class Analytics {
147170
try {
148171
const distinctId = this.distinctId ?? this.anonymousId;
149172
const result = await this.client.getAllFlagsAndPayloads(distinctId, {
150-
personProperties: { $app_name: this.appName },
173+
personProperties: this.flagPersonProperties(),
151174
});
152175
const flags = result.featureFlags ?? {};
153176
const out: Record<string, string> = {};

0 commit comments

Comments
 (0)