Repository: cline/cline
Pull request #3765: Use identify to enhance distinct user segmentation
Author: login: trevhud
Created: 2025-05-23T06:21:11Z | Merged: 2025-05-26T19:12:24Z

### Description

Use identify to enhance distinct user segmentation

### Test Procedure

Test on events 

### Type of Change

<!-- Put an 'x' in all boxes that apply -->

-   [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
-   [ ] ✨ New feature (non-breaking change which adds functionality)
-   [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
-   [ ] ♻️ Refactor Changes
-   [ ] 💅 Cosmetic Changes
-   [ ] 📚 Documentation update
-   [x] 🏃 Workflow Changes

### Pre-flight Checklist

<!-- Put an 'x' in all boxes that apply -->

-   [x] Changes are limited to a single feature, bugfix or chore (split larger changes into separate PRs)
-   [ ] Tests are passing (`npm test`) and code is formatted and linted (`npm run format && npm run lint`)
-   [ ] I have created a changeset using `npm run changeset` (required for user-facing changes)
-   [ ] I have reviewed [contributor guidelines](https://github.com/cline/cline/blob/main/CONTRIBUTING.md)

<!-- ELLIPSIS_HIDDEN -->


----

> [!IMPORTANT]
> Enhance user segmentation by using distinct identifiers for telemetry, updating initialization, and cleaning up telemetry code.
> 
>   - **Telemetry Enhancements**:
>     - Use `telemetryService.getDistinctId()` instead of `vscode.env.machineId` in `index.ts` for distinct user identification.
>     - Add `captureExtensionActivated()` in `TelemetryService` to log extension activation with a distinct ID.
>     - Remove `defaultOptIn` from `PostHogClientProvider.ts`.
>   - **Settings and Initialization**:
>     - Add backup `installId` generation in `extension.ts` if `vscMachineID` fails.
>     - Update `CustomPostHogProvider.tsx` to handle telemetry settings and distinct ID.
>   - **Code Cleanup**:
>     - Remove unused UI event tracking methods in `TelemetryService.ts`.
>     - Simplify telemetry event constants and methods in `TelemetryService.ts`.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=cline%2Fcline&utm_source=github&utm_medium=referral)<sup> for e038d3b15074803032ea60e086e114cefac94865. You can [customize](https://app.ellipsis.dev/cline/settings/summaries) this summary. It will automatically update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->


Diff:
diff --git a/.changeset/good-hounds-mate.md b/.changeset/good-hounds-mate.md
new file mode 100644
index 00000000000..95a12746a6a
--- /dev/null
+++ b/.changeset/good-hounds-mate.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+Use identify to enhance distinct user segmentation
diff --git a/src/core/controller/index.ts b/src/core/controller/index.ts
index 7ee106d078d..87532a1eba1 100644
--- a/src/core/controller/index.ts
+++ b/src/core/controller/index.ts
@@ -261,13 +261,6 @@ export class Controller {
 						}
 					}
 				})
-
-				// If user already opted in to telemetry, enable telemetry service
-				this.getStateToPostToWebview().then((state) => {
-					const { telemetrySetting } = state
-					const isOptedIn = telemetrySetting !== "disabled"
-					telemetryService.updateTelemetryState(isOptedIn)
-				})
 				break
 			case "showChatView": {
 				this.postMessageToWebview({
@@ -810,6 +803,7 @@ export class Controller {
 			const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
 				headers: {
 					"Content-Type": "application/json",
+					"User-Agent": "cline-vscode-extension",
 				},
 			})
 
@@ -1287,7 +1281,7 @@ export class Controller {
 			telemetrySetting,
 			planActSeparateModelsSetting,
 			enableCheckpointsSetting: enableCheckpointsSetting ?? true,
-			vscMachineId: vscode.env.machineId,
+			distinctId: telemetryService.distinctId,
 			globalClineRulesToggles: globalClineRulesToggles || {},
 			localClineRulesToggles: localClineRulesToggles || {},
 			localWindsurfRulesToggles: localWindsurfRulesToggles || {},
diff --git a/src/extension.ts b/src/extension.ts
index 9a2903daf6c..6e93c97351b 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -14,6 +14,7 @@ import { Controller } from "./core/controller"
 import { ErrorService } from "./services/error/ErrorService"
 import { initializeTestMode, cleanupTestMode } from "./services/test/TestMode"
 import { telemetryService } from "./services/posthog/telemetry/TelemetryService"
+import { v4 as uuidv4 } from "uuid"
 
 /*
 Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -75,6 +76,16 @@ export async function activate(context: vscode.ExtensionContext) {
 		console.error(`Error during post-update actions: ${errorMessage}, Stack trace: ${error.stack}`)
 	}
 
+	// backup id in case vscMachineID doesn't work
+	let installId = context.globalState.get<string>("installId")
+
+	if (!installId) {
+		installId = uuidv4()
+		await context.globalState.update("installId", installId)
+	}
+
+	telemetryService.captureExtensionActivated(installId)
+
 	context.subscriptions.push(
 		vscode.commands.registerCommand("cline.plusButtonClicked", async (webview: any) => {
 			const openChat = async (instance?: WebviewProvider) => {
diff --git a/src/services/posthog/PostHogClientProvider.ts b/src/services/posthog/PostHogClientProvider.ts
index 81977946cb5..dab585aa9d4 100644
--- a/src/services/posthog/PostHogClientProvider.ts
+++ b/src/services/posthog/PostHogClientProvider.ts
@@ -9,7 +9,6 @@ class PostHogClientProvider {
 		this.client = new PostHog(posthogConfig.apiKey, {
 			host: posthogConfig.host,
 			enableExceptionAutocapture: false,
-			defaultOptIn: false,
 		})
 	}
 
diff --git a/src/services/posthog/telemetry/TelemetryService.ts b/src/services/posthog/telemetry/TelemetryService.ts
index 6a064051cfa..a2ed2e08dea 100644
--- a/src/services/posthog/telemetry/TelemetryService.ts
+++ b/src/services/posthog/telemetry/TelemetryService.ts
@@ -7,7 +7,7 @@ import type { BrowserSettings } from "@shared/BrowserSettings"
 import { posthogClientProvider } from "../PostHogClientProvider"
 
 /**
- * PostHogClient handles telemetry event tracking for the Cline extension
+ * TelemetryService handles telemetry event tracking for the Cline extension
  * Uses PostHog analytics to track user interactions and system events
  * Respects user privacy settings and VSCode's global telemetry configuration
  */
@@ -29,7 +29,7 @@ interface Collection {
  */
 type TelemetryCategory = "checkpoints" | "browser"
 
-class PostHogClient {
+class TelemetryService {
 	// Map to control specific telemetry categories (event types)
 	private telemetryCategoryEnabled: Map<TelemetryCategory, boolean> = new Map([
 		["checkpoints", false], // Checkpoints telemetry disabled
@@ -41,6 +41,11 @@ class PostHogClient {
 	// Event constants for tracking user interactions and system events
 	private static readonly EVENTS = {
 		// Task-related events for tracking conversation and execution flow
+
+		USER: {
+			OPT_OUT: "user.opt_out",
+			EXTENSION_ACTIVATED: "user.extension_activated",
+		},
 		TASK: {
 			// Tracks when a new task/conversation is started
 			CREATED: "task.created",
@@ -83,37 +88,19 @@ class PostHogClient {
 		},
 		// UI interaction events for tracking user engagement
 		UI: {
-			// Tracks when user switches between API providers
-			PROVIDER_SWITCH: "ui.provider_switch",
-			// Tracks when images are attached to a conversation
-			IMAGE_ATTACHED: "ui.image_attached",
-			// Tracks general button click interactions
-			BUTTON_CLICK: "ui.button_click",
-			// Tracks when the marketplace view is opened
-			MARKETPLACE_OPENED: "ui.marketplace_opened",
-			// Tracks when settings panel is opened
-			SETTINGS_OPENED: "ui.settings_opened",
-			// Tracks when task history view is opened
-			HISTORY_OPENED: "ui.history_opened",
-			// Tracks when a task is removed from history
-			TASK_POPPED: "ui.task_popped",
 			// Tracks when a different model is selected
 			MODEL_SELECTED: "ui.model_selected",
-			// Tracks when planning mode is toggled on
-			PLAN_MODE_TOGGLED: "ui.plan_mode_toggled",
-			// Tracks when action mode is toggled on
-			ACT_MODE_TOGGLED: "ui.act_mode_toggled",
 			// Tracks when users use the "favorite" button in the model picker
 			MODEL_FAVORITE_TOGGLED: "ui.model_favorite_toggled",
 		},
 	}
 
-	/** Singleton instance of the PostHogClient */
-	private static instance: PostHogClient
+	/** Singleton instance of the TelemetryService */
+	private static instance: TelemetryService
 	/** PostHog client instance for sending analytics events */
 	private client: PostHog
 	/** Unique identifier for the current VSCode instance */
-	private distinctId: string = vscode.env.machineId
+	public distinctId: string = vscode.env.machineId
 	/** Whether telemetry is currently enabled based on user and VSCode settings */
 	private telemetryEnabled: boolean = false
 	/** Current version of the extension */
@@ -129,14 +116,18 @@ class PostHogClient {
 		this.client = posthogClientProvider.getClient()
 	}
 
+	private setDistinctId(installId: string) {
+		if (this.distinctId === "someValue.machineId") {
+			this.distinctId = installId
+		}
+	}
+
 	/**
 	 * Updates the telemetry state based on user preferences and VSCode settings
 	 * Only enables telemetry if both VSCode global telemetry is enabled and user has opted in
 	 * @param didUserOptIn Whether the user has explicitly opted into telemetry
 	 */
-	public updateTelemetryState(didUserOptIn: boolean): void {
-		this.telemetryEnabled = false
-
+	public async updateTelemetryState(didUserOptIn: boolean): Promise<void> {
 		// First check global telemetry level - telemetry should only be enabled when level is "all"
 		const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get<string>("telemetryLevel", "all")
 		const globalTelemetryEnabled = telemetryLevel === "all"
@@ -144,25 +135,53 @@ class PostHogClient {
 		// We only enable telemetry if global vscode telemetry is enabled
 		if (globalTelemetryEnabled) {
 			this.telemetryEnabled = didUserOptIn
+		} else {
+			// Show warning to user that global telemetry is disabled
+			void vscode.window
+				.showWarningMessage(
+					"VSCode telemetry is disabled. To enable telemetry for this extension, first enable VSCode telemetry in settings.",
+					"Open Settings",
+				)
+				.then((selection) => {
+					if (selection === "Open Settings") {
+						void vscode.commands.executeCommand("workbench.action.openSettings", "telemetry.telemetryLevel")
+					}
+				})
 		}
 
 		// Update PostHog client state based on telemetry preference
 		if (this.telemetryEnabled) {
 			this.client.optIn()
+			this.client.identify({ distinctId: this.distinctId })
 		} else {
+			this.client.capture({
+				distinctId: this.distinctId,
+				event: TelemetryService.EVENTS.USER.OPT_OUT,
+				properties: this.addProperties({}),
+			})
+
+			await new Promise((resolve) => setTimeout(resolve, 1000)) // Delay 1 second before opting out
 			this.client.optOut()
 		}
 	}
 
 	/**
-	 * Gets or creates the singleton instance of PostHogClient
-	 * @returns The PostHogClient instance
+	 * Gets or creates the singleton instance of TelemetryService
+	 * @returns The TelemetryService instance
 	 */
-	public static getInstance(): PostHogClient {
-		if (!PostHogClient.instance) {
-			PostHogClient.instance = new PostHogClient()
+	public static getInstance(): TelemetryService {
+		if (!TelemetryService.instance) {
+			TelemetryService.instance = new TelemetryService()
+		}
+		return TelemetryService.instance
+	}
+
+	private addProperties(properties: any): any {
+		return {
+			...properties,
+			extension_version: this.version,
+			is_dev: this.isDev,
 		}
-		return PostHogClient.instance
 	}
 
 	/**
@@ -171,13 +190,14 @@ class PostHogClient {
 	 * @param collect If true, store the event in collectedEvents instead of sending to PostHog
 	 */
 	public capture(event: { event: string; properties?: any }, collect: boolean = false): void {
-		const taskId = event.properties.taskId
-		const propertiesWithVersion = {
-			...event.properties,
-			extension_version: this.version,
-			is_dev: this.isDev,
+		if (!this.telemetryEnabled) {
+			return
 		}
-		if (collect) {
+		const taskId = event.properties.taskId
+
+		const propertiesWithVersion = this.addProperties(event.properties)
+
+		if (collect && taskId) {
 			const existingTask = this.collectedTasks.find((task) => task.taskId === taskId)
 			if (existingTask) {
 				existingTask.collection.push({
@@ -195,11 +215,20 @@ class PostHogClient {
 					],
 				})
 			}
-		} else if (this.telemetryEnabled) {
+		} else {
 			this.client.capture({ distinctId: this.distinctId, event: event.event, properties: propertiesWithVersion })
 		}
 	}
 
+	public captureExtensionActivated(installId: string) {
+		this.setDistinctId(installId)
+
+		if (this.telemetryEnabled) {
+			this.client.identify({ distinctId: this.distinctId })
+			this.client.capture({ distinctId: this.distinctId, event: TelemetryService.EVENTS.USER.EXTENSION_ACTIVATED })
+		}
+	}
+
 	// Task events
 	/**
 	 * Records when a new task/conversation is started
@@ -210,7 +239,7 @@ class PostHogClient {
 	public captureTaskCreated(taskId: string, apiProvider?: string, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.CREATED,
+				event: TelemetryService.EVENTS.TASK.CREATED,
 				properties: { taskId, apiProvider },
 			},
 			collect,
@@ -226,7 +255,7 @@ class PostHogClient {
 	public captureTaskRestarted(taskId: string, apiProvider?: string, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.RESTARTED,
+				event: TelemetryService.EVENTS.TASK.RESTARTED,
 				properties: { taskId, apiProvider },
 			},
 			collect,
@@ -241,7 +270,7 @@ class PostHogClient {
 	public captureTaskCompleted(taskId: string, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.COMPLETED,
+				event: TelemetryService.EVENTS.TASK.COMPLETED,
 				properties: { taskId },
 			},
 			collect,
@@ -278,7 +307,7 @@ class PostHogClient {
 
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.CONVERSATION_TURN,
+				event: TelemetryService.EVENTS.TASK.CONVERSATION_TURN,
 				properties,
 			},
 			collect,
@@ -295,7 +324,7 @@ class PostHogClient {
 	public captureTokenUsage(taskId: string, tokensIn: number, tokensOut: number, model: string, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.TOKEN_USAGE,
+				event: TelemetryService.EVENTS.TASK.TOKEN_USAGE,
 				properties: {
 					taskId,
 					tokensIn,
@@ -315,7 +344,7 @@ class PostHogClient {
 	public captureModeSwitch(taskId: string, mode: "plan" | "act", collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.MODE_SWITCH,
+				event: TelemetryService.EVENTS.TASK.MODE_SWITCH,
 				properties: {
 					taskId,
 					mode,
@@ -334,7 +363,7 @@ class PostHogClient {
 		console.info("TelemetryService: Capturing task feedback", { taskId, feedbackType })
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.FEEDBACK,
+				event: TelemetryService.EVENTS.TASK.FEEDBACK,
 				properties: {
 					taskId,
 					feedbackType,
@@ -355,7 +384,7 @@ class PostHogClient {
 	public captureToolUsage(taskId: string, tool: string, autoApproved: boolean, success: boolean, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.TOOL_USED,
+				event: TelemetryService.EVENTS.TASK.TOOL_USED,
 				properties: {
 					taskId,
 					tool,
@@ -385,7 +414,7 @@ class PostHogClient {
 
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.CHECKPOINT_USED,
+				event: TelemetryService.EVENTS.TASK.CHECKPOINT_USED,
 				properties: {
 					taskId,
 					action,
@@ -396,135 +425,6 @@ class PostHogClient {
 		)
 	}
 
-	// UI events
-	/**
-	 * Records when the user switches between different API providers
-	 * @param from Previous provider name
-	 * @param to New provider name
-	 * @param location Where the switch occurred (settings panel or bottom bar)
-	 * @param taskId Optional task identifier if switch occurred during a task
-	 */
-	public captureProviderSwitch(
-		from: string,
-		to: string,
-		location: "settings" | "bottom",
-		taskId?: string,
-		collect: boolean = false,
-	) {
-		this.capture(
-			{
-				event: PostHogClient.EVENTS.UI.PROVIDER_SWITCH,
-				properties: {
-					from,
-					to,
-					location,
-					taskId,
-				},
-			},
-			collect,
-		)
-	}
-
-	/**
-	 * Records when images are attached to a conversation
-	 * @param taskId Unique identifier for the task
-	 * @param imageCount Number of images attached
-	 */
-	public captureImageAttached(taskId: string, imageCount: number, collect: boolean = false) {
-		this.capture(
-			{
-				event: PostHogClient.EVENTS.UI.IMAGE_ATTACHED,
-				properties: {
-					taskId,
-					imageCount,
-				},
-			},
-			collect,
-		)
-	}
-
-	/**
-	 * Records general button click interactions in the UI
-	 * @param button Identifier for the button that was clicked
-	 * @param taskId Optional task identifier if click occurred during a task
-	 */
-	public captureButtonClick(button: string, taskId?: string, collect: boolean = false) {
-		this.capture(
-			{
-				event: PostHogClient.EVENTS.UI.BUTTON_CLICK,
-				properties: {
-					button,
-					taskId,
-				},
-			},
-			collect,
-		)
-	}
-
-	/**
-	 * Records when the marketplace view is opened
-	 * @param taskId Optional task identifier if marketplace was opened during a task
-	 */
-	public captureMarketplaceOpened(taskId?: string, collect: boolean = false) {
-		this.capture(
-			{
-				event: PostHogClient.EVENTS.UI.MARKETPLACE_OPENED,
-				properties: {
-					taskId,
-				},
-			},
-			collect,
-		)
-	}
-
-	/**
-	 * Records when the settings panel is opened
-	 * @param taskId Optional task identifier if settings were opened during a task
-	 */
-	public captureSettingsOpened(taskId?: string, collect: boolean = false) {
-		this.capture(
-			{
-				event: PostHogClient.EVENTS.UI.SETTINGS_OPENED,
-				properties: {
-					taskId,
-				},
-			},
-			collect,
-		)
-	}
-
-	/**
-	 * Records when the task history view is opened
-	 * @param taskId Optional task identifier if history was opened during a task
-	 */
-	public captureHistoryOpened(taskId?: string, collect: boolean = false) {
-		this.capture(
-			{
-				event: PostHogClient.EVENTS.UI.HISTORY_OPENED,
-				properties: {
-					taskId,
-				},
-			},
-			collect,
-		)
-	}
-
-	/**
-	 * Records when a task is removed from the task history
-	 * @param taskId Unique identifier for the task being removed
-	 */
-	public captureTaskPopped(taskId: string, collect: boolean = false) {
-		this.capture(
-			{
-				event: PostHogClient.EVENTS.UI.TASK_POPPED,
-				properties: {
-					taskId,
-				},
-			},
-			collect,
-		)
-	}
-
 	/**
 	 * Records when a diff edit (replace_in_file) operation fails
 	 * @param taskId Unique identifier for the task
@@ -533,7 +433,7 @@ class PostHogClient {
 	public captureDiffEditFailure(taskId: string, modelId: string, errorType?: string, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.DIFF_EDIT_FAILED,
+				event: TelemetryService.EVENTS.TASK.DIFF_EDIT_FAILED,
 				properties: {
 					taskId,
 					errorType,
@@ -553,7 +453,7 @@ class PostHogClient {
 	public captureModelSelected(model: string, provider: string, taskId?: string, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.UI.MODEL_SELECTED,
+				event: TelemetryService.EVENTS.UI.MODEL_SELECTED,
 				properties: {
 					model,
 					provider,
@@ -571,7 +471,7 @@ class PostHogClient {
 	public captureHistoricalTaskLoaded(taskId: string, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.HISTORICAL_LOADED,
+				event: TelemetryService.EVENTS.TASK.HISTORICAL_LOADED,
 				properties: {
 					taskId,
 				},
@@ -587,7 +487,7 @@ class PostHogClient {
 	public captureRetryClicked(taskId: string, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.RETRY_CLICKED,
+				event: TelemetryService.EVENTS.TASK.RETRY_CLICKED,
 				properties: {
 					taskId,
 				},
@@ -608,7 +508,7 @@ class PostHogClient {
 
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.BROWSER_TOOL_START,
+				event: TelemetryService.EVENTS.TASK.BROWSER_TOOL_START,
 				properties: {
 					taskId,
 					viewport: browserSettings.viewport,
@@ -641,7 +541,7 @@ class PostHogClient {
 
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.BROWSER_TOOL_END,
+				event: TelemetryService.EVENTS.TASK.BROWSER_TOOL_END,
 				properties: {
 					taskId,
 					actionCount: stats.actionCount,
@@ -679,7 +579,7 @@ class PostHogClient {
 
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.BROWSER_ERROR,
+				event: TelemetryService.EVENTS.TASK.BROWSER_ERROR,
 				properties: {
 					taskId,
 					errorType,
@@ -701,7 +601,7 @@ class PostHogClient {
 	public captureOptionSelected(taskId: string, qty: number, mode: "plan" | "act", collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.OPTION_SELECTED,
+				event: TelemetryService.EVENTS.TASK.OPTION_SELECTED,
 				properties: {
 					taskId,
 					qty,
@@ -721,7 +621,7 @@ class PostHogClient {
 	public captureOptionsIgnored(taskId: string, qty: number, mode: "plan" | "act", collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.OPTIONS_IGNORED,
+				event: TelemetryService.EVENTS.TASK.OPTIONS_IGNORED,
 				properties: {
 					taskId,
 					qty,
@@ -758,7 +658,7 @@ class PostHogClient {
 	) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.TASK.GEMINI_API_PERFORMANCE,
+				event: TelemetryService.EVENTS.TASK.GEMINI_API_PERFORMANCE,
 				properties: {
 					taskId,
 					modelId,
@@ -777,7 +677,7 @@ class PostHogClient {
 	public captureModelFavoritesUsage(model: string, isFavorited: boolean, collect: boolean = false) {
 		this.capture(
 			{
-				event: PostHogClient.EVENTS.UI.MODEL_FAVORITE_TOGGLED,
+				event: TelemetryService.EVENTS.UI.MODEL_FAVORITE_TOGGLED,
 				properties: {
 					model,
 					isFavorited,
@@ -806,13 +706,17 @@ class PostHogClient {
 	}
 
 	public async sendCollectedEvents(taskId?: string): Promise<void> {
+		if (!this.telemetryEnabled) {
+			return
+		}
+
 		if (this.collectedTasks.length > 0) {
 			if (taskId) {
 				const task = this.collectedTasks.find((t) => t.taskId === taskId)
 				if (task) {
 					this.capture(
 						{
-							event: PostHogClient.EVENTS.TASK.TASK_COLLECTION,
+							event: TelemetryService.EVENTS.TASK.TASK_COLLECTION,
 							properties: { taskId, events: task.collection },
 						},
 						false,
@@ -823,7 +727,7 @@ class PostHogClient {
 				for (const task of this.collectedTasks) {
 					this.capture(
 						{
-							event: PostHogClient.EVENTS.TASK.TASK_COLLECTION,
+							event: TelemetryService.EVENTS.TASK.TASK_COLLECTION,
 							properties: { taskId: task.taskId, events: task.collection },
 						},
 						false,
@@ -839,4 +743,4 @@ class PostHogClient {
 	}
 }
 
-export const telemetryService = PostHogClient.getInstance()
+export const telemetryService = TelemetryService.getInstance()
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index 48a4b6315bb..70b8b776d46 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -137,7 +137,7 @@ export interface ExtensionState {
 		photoURL: string | null
 	}
 	version: string
-	vscMachineId: string
+	distinctId: string
 	globalClineRulesToggles: ClineRulesToggles
 	localClineRulesToggles: ClineRulesToggles
 	localWorkflowToggles: ClineRulesToggles
diff --git a/webview-ui/src/CustomPostHogProvider.tsx b/webview-ui/src/CustomPostHogProvider.tsx
index 1642eb6a5f9..12c1013243c 100644
--- a/webview-ui/src/CustomPostHogProvider.tsx
+++ b/webview-ui/src/CustomPostHogProvider.tsx
@@ -5,32 +5,44 @@ import { posthogConfig } from "@shared/services/config/posthog-config"
 import { useExtensionState } from "./context/ExtensionStateContext"
 
 export function CustomPostHogProvider({ children }: { children: ReactNode }) {
-	const { telemetrySetting, vscMachineId } = useExtensionState()
+	const { telemetrySetting, distinctId, version } = useExtensionState()
 	const isTelemetryEnabled = telemetrySetting !== "disabled"
 
 	useEffect(() => {
-		if (vscMachineId.length === 0) {
-			return
-		}
-
 		posthog.init(posthogConfig.apiKey, {
 			api_host: posthogConfig.host,
 			ui_host: posthogConfig.uiHost,
-			opt_out_capturing_by_default: true,
 			disable_session_recording: true,
 			capture_pageview: false,
 			capture_dead_clicks: true,
-			bootstrap: {
-				distinctID: vscMachineId,
+		})
+	}, [])
+
+	useEffect(() => {
+		if (distinctId.length === 0 || version.length === 0) {
+			return
+		}
+
+		posthog.set_config({
+			before_send: (payload: any) => {
+				if (payload?.properties) {
+					payload.properties.extension_version = version
+					payload.properties.distinct_id = distinctId
+				}
+				return payload
 			},
 		})
 
-		if (isTelemetryEnabled) {
+		const optedIn = posthog.has_opted_in_capturing()
+		const optedOut = posthog.has_opted_out_capturing()
+
+		if (isTelemetryEnabled && !optedIn) {
 			posthog.opt_in_capturing()
-		} else {
+			posthog.identify(distinctId)
+		} else if (!isTelemetryEnabled && !optedOut) {
 			posthog.opt_out_capturing()
 		}
-	}, [isTelemetryEnabled, vscMachineId])
+	}, [isTelemetryEnabled, distinctId, version])
 
 	return <PostHogProvider client={posthog}>{children}</PostHogProvider>
 }
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index fe33eeb637f..0f3848ff87e 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -607,7 +607,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
 										<div className="mb-[5px]">
 											<VSCodeCheckbox
 												className="mb-[5px]"
-												checked={telemetrySetting === "enabled"}
+												checked={telemetrySetting !== "disabled"}
 												onChange={(e: any) => {
 													const checked = e.target.checked === true
 													setTelemetrySetting(checked ? "enabled" : "disabled")
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index 6433c99b1a5..060d79d9947 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -156,7 +156,7 @@ export const ExtensionStateContextProvider: React.FC<{
 		chatSettings: DEFAULT_CHAT_SETTINGS,
 		platform: DEFAULT_PLATFORM,
 		telemetrySetting: "unset",
-		vscMachineId: "",
+		distinctId: "",
 		planActSeparateModelsSetting: true,
 		enableCheckpointsSetting: true,
 		globalClineRulesToggles: {},


Reviews:
user: login: saoudrizwan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T18:53:40Z
user: login: saoudrizwan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T18:54:09Z
user: login: trevhud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T19:08:23Z
user: login: trevhud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T19:08:36Z
user: login: saoudrizwan
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T19:16:00Z
user: login: dcbartlett
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T20:09:14Z
user: login: trevhud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T21:09:11Z
user: login: canvrno
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T23:01:58Z
user: login: dcbartlett
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-24T00:36:55Z
user: login: trevhud
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-24T21:59:22Z
user: login: dcbartlett
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T19:09:27Z
user: login: dcbartlett
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T19:09:45Z

Review comments:
diff_hunk: @@ -261,13 +261,6 @@ export class Controller {
 						}
 					}
 				})
-
path: src/core/controller/index.ts
user: login: saoudrizwan
body: why are we removing this?
created_at: 2025-05-23T18:53:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,6 +76,16 @@ export async function activate(context: vscode.ExtensionContext) {
 		console.error(`Error during post-update actions: ${errorMessage}, Stack trace: ${error.stack}`)
 	}
 
+	// backup id in case vscMachineID doesn't work
+	let installId = context.globalState.get<string>("installId")
+
+	if (!installId) {
+		installId = uuidv4()
path: src/extension.ts
user: login: saoudrizwan
body: why are we using uuid instead of vscmachineid?
created_at: 2025-05-23T18:54:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -261,13 +261,6 @@ export class Controller {
 						}
 					}
 				})
-
path: src/core/controller/index.ts
user: login: trevhud
body: no longer needed
created_at: 2025-05-23T19:08:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,6 +76,16 @@ export async function activate(context: vscode.ExtensionContext) {
 		console.error(`Error during post-update actions: ${errorMessage}, Stack trace: ${error.stack}`)
 	}
 
+	// backup id in case vscMachineID doesn't work
+	let installId = context.globalState.get<string>("installId")
+
+	if (!installId) {
+		installId = uuidv4()
path: src/extension.ts
user: login: trevhud
body: It's a backup for when machineId is not available
created_at: 2025-05-23T19:08:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -261,13 +261,6 @@ export class Controller {
 						}
 					}
 				})
-
path: src/core/controller/index.ts
user: login: dcbartlett
body: Can you explain a bit more as to why this is no longer needed?  What purpose did it provide that we no longer require?
created_at: 2025-05-23T20:09:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -261,13 +261,6 @@ export class Controller {
 						}
 					}
 				})
-
path: src/core/controller/index.ts
user: login: trevhud
body: It's always been a bit redundant. But before when we did not opt the user in by default we had to trigger an opt in when the webview loaded. Now it just uses the user's saved preference
created_at: 2025-05-23T21:09:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -261,13 +261,6 @@ export class Controller {
 						}
 					}
 				})
-
path: src/core/controller/index.ts
user: login: dcbartlett
body: OK, thanks for the info.
created_at: 2025-05-24T00:34:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,40 +116,72 @@ class PostHogClient {
 		this.client = posthogClientProvider.getClient()
 	}
 
+	private setDistinctId(installId: string) {
+		if (this.distinctId === "someValue.machineId") {
path: src/services/posthog/telemetry/TelemetryService.ts
user: login: dcbartlett
body: Based on my testing in this branch, and some looking at the code here.  This looks like it will never be a true condition.  Can you explain how this works and where we would ever match true for distinctId to be `someValue.machineId`
created_at: 2025-05-24T00:36:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,40 +116,72 @@ class PostHogClient {
 		this.client = posthogClientProvider.getClient()
 	}
 
+	private setDistinctId(installId: string) {
+		if (this.distinctId === "someValue.machineId") {
path: src/services/posthog/telemetry/TelemetryService.ts
user: login: trevhud
body: @dcbartlett I don't know why but there is a portion of the events coming in that have that string as their vscMachineId. The problem with that is that multiple users could be seen as one distinct users, resulting in bad data. I added a backup uuid that is only used if the vscMachineId matches that string.

<img width="1425" alt="Screenshot 2025-05-24 at 2 57 20 PM" src="https://github.com/user-attachments/assets/fc2aacee-e876-4b9f-9651-c42f4dc27cde" />

created_at: 2025-05-24T21:59:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,40 +116,72 @@ class PostHogClient {
 		this.client = posthogClientProvider.getClient()
 	}
 
+	private setDistinctId(installId: string) {
+		if (this.distinctId === "someValue.machineId") {
path: src/services/posthog/telemetry/TelemetryService.ts
user: login: dcbartlett
body: OK.  We should try to find out what actually causes this and make a comment about that.  I'll approve for now, but please follow up with a comment explaining why this is needed, so it's not ripped out in the future.
created_at: 2025-05-26T19:09:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-23T06:21:14Z
body: ###  🦋  Changeset detected

Latest commit: e038d3b15074803032ea60e086e114cefac94865

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name       | Type  |
| ---------- | ----- |
| claude-dev | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/cline/cline/new/trevhud/identify?filename=.changeset/twelve-queens-vanish.md&value=---%0A%22claude-dev%22%3A%20patch%0A---%0A%0AUse%20identify%20to%20enhance%20distinct%20user%20segmentation%0A)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-23T06:27:07Z
body: <!-- COVERAGE_REPORT -->
## Coverage Report

### Extension Coverage

Base branch: 0%

PR branch: 45%

✅ Coverage increased or remained the same

### Webview Coverage

Base branch: 14%

PR branch: 14%

⚠️ **Warning: Coverage decreased by 0.01%**

Consider adding tests to cover your changes.

### Overall Assessment

⚠️ **Test coverage has decreased in this PR**

Please consider adding tests to maintain or improve coverage.



<sub>Last updated: 2025-05-23T20:38:05.054865</sub>
author_association: CONTRIBUTOR