Skip to content

Commit 5689324

Browse files
committed
Add optional opt-in telemetry to help fix bugs and improve product
1 parent 35a64a3 commit 5689324

File tree

11 files changed

+233
-17
lines changed

11 files changed

+233
-17
lines changed

docs/PRIVACY.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ Cline functions solely as a client-side VS Code extension that facilitates commu
4242
1. **Local-Only Processing**:
4343

4444
- All operations happen on your local machine
45-
- No central servers or data collection
46-
- No telemetry or usage statistics gathered
45+
- No central servers or data collection by default
46+
- Anonymous telemetry and usage statistics are only collected if you explicitly opt in
4747
- No account creation required
4848

4949
2. **API Key Security**:
@@ -73,7 +73,8 @@ When you request assistance:
7373

7474
- Error logs are processed locally
7575
- No automatic error reporting to Cline
76-
- You control what information to include when reporting issues
76+
- Optional anonymous telemetry and error reporting via PostHog if you opt in
77+
- You control what information to include when manually reporting issues
7778

7879
## Children's Privacy
7980

@@ -90,6 +91,22 @@ We will post any changes to this policy on our GitHub repository. Significant ch
9091
- You can inspect exactly what data is being sent to AI providers
9192
- Enterprise users can implement additional access controls through VS Code
9293

94+
## Telemetry & Usage Statistics
95+
96+
If you choose to opt in to anonymous telemetry:
97+
98+
- Basic usage statistics and error reports are collected via PostHog
99+
- A stable, anonymous identifier (VS Code's `machineId`) is used to understand unique usage patterns
100+
- This identifier is not linked to any personal information
101+
- It helps us understand how features are used across sessions
102+
- It cannot be used to identify you personally
103+
- All data is anonymized and cannot be linked to individual users
104+
- No code content or sensitive information is ever included
105+
- You can opt out at any time through:
106+
- VS Code Settings > Cline > Enable Telemetry
107+
- VS Code Settings > Telemetry > Telemetry Level (setting this to anything other than "all" will disable Cline's telemetry)
108+
- Collected data helps us improve the extension's functionality and stability
109+
93110
## Contact Us
94111

95112
For privacy-related questions or concerns:

package-lock.json

Lines changed: 15 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@
186186
"type": "boolean",
187187
"default": true,
188188
"description": "Controls whether the MCP Marketplace is enabled."
189+
},
190+
"cline.enableTelemetry": {
191+
"type": "boolean",
192+
"default": null,
193+
"markdownDescription": "Allow anonymous usage and error reporting to help improve Cline. No code, prompts, or personal information is ever sent. See our [privacy policy](https://github.com/cline/cline/blob/main/docs/PRIVACY.md) for details."
189194
}
190195
}
191196
}
@@ -268,6 +273,7 @@
268273
"os-name": "^6.0.0",
269274
"p-wait-for": "^5.0.2",
270275
"pdf-parse": "^1.1.1",
276+
"posthog-node": "^4.7.0",
271277
"puppeteer-chromium-resolver": "^23.0.0",
272278
"puppeteer-core": "^23.4.0",
273279
"serialize-error": "^11.0.3",

src/core/webview/ClineProvider.ts

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Anthropic } from "@anthropic-ai/sdk"
22
import axios from "axios"
3-
import fs from "fs/promises"
4-
import os from "os"
53
import crypto from "crypto"
64
import { execa } from "execa"
5+
import fs from "fs/promises"
6+
import os from "os"
77
import pWaitFor from "p-wait-for"
88
import * as path from "path"
99
import * as vscode from "vscode"
@@ -13,26 +13,25 @@ import { openFile, openImage } from "../../integrations/misc/open-file"
1313
import { selectImages } from "../../integrations/misc/process-images"
1414
import { getTheme } from "../../integrations/theme/getTheme"
1515
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
16-
import { McpHub } from "../../services/mcp/McpHub"
17-
import { McpDownloadResponse, McpMarketplaceCatalog, McpMarketplaceItem, McpServer } from "../../shared/mcp"
1816
import { FirebaseAuthManager, UserInfo } from "../../services/auth/FirebaseAuthManager"
17+
import { McpHub } from "../../services/mcp/McpHub"
1918
import { ApiProvider, ModelInfo } from "../../shared/api"
2019
import { findLast } from "../../shared/array"
20+
import { AutoApprovalSettings, DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings"
21+
import { BrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../shared/BrowserSettings"
22+
import { ChatContent } from "../../shared/ChatContent"
23+
import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings"
2124
import { ExtensionMessage, ExtensionState, Platform } from "../../shared/ExtensionMessage"
2225
import { HistoryItem } from "../../shared/HistoryItem"
26+
import { McpDownloadResponse, McpMarketplaceCatalog, McpServer } from "../../shared/mcp"
2327
import { ClineCheckpointRestore, WebviewMessage } from "../../shared/WebviewMessage"
2428
import { fileExistsAtPath } from "../../utils/fs"
29+
import { searchCommits } from "../../utils/git"
2530
import { Cline } from "../Cline"
2631
import { openMention } from "../mentions"
2732
import { getNonce } from "./getNonce"
2833
import { getUri } from "./getUri"
29-
import { AutoApprovalSettings, DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings"
30-
import { BrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../shared/BrowserSettings"
31-
import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings"
32-
import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider"
33-
import { searchCommits } from "../../utils/git"
34-
import { ChatContent } from "../../shared/ChatContent"
35-
import { getShell } from "../../utils/shell"
34+
import { telemetryService } from "../../services/telemetry/TelemetryService"
3635

3736
/*
3837
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -280,6 +279,16 @@ export class ClineProvider implements vscode.WebviewViewProvider {
280279
task,
281280
images,
282281
)
282+
283+
// New task started
284+
if (telemetryService.isTelemetryEnabled()) {
285+
telemetryService.capture({
286+
event: "New task started",
287+
properties: {
288+
apiProvider: apiConfiguration.apiProvider,
289+
},
290+
})
291+
}
283292
}
284293

285294
async initClineWithHistoryItem(historyItem: HistoryItem) {
@@ -829,6 +838,21 @@ export class ClineProvider implements vscode.WebviewViewProvider {
829838
)
830839
break
831840
}
841+
// telemetry
842+
case "openTelemetrySettings": {
843+
await vscode.commands.executeCommand(
844+
"workbench.action.openSettings",
845+
"@ext:saoudrizwan.claude-dev cline.telemetryOptIn",
846+
)
847+
break
848+
}
849+
case "telemetryOptIn": {
850+
if (message.bool !== undefined) {
851+
await vscode.workspace.getConfiguration("cline").update("enableTelemetry", message.bool, true)
852+
await this.postStateToWebview()
853+
}
854+
break
855+
}
832856
// Add more switch case statements here as more webview message commands
833857
// are created within the webview context (i.e. inside media/main.js)
834858
}
@@ -1594,6 +1618,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
15941618
userInfo,
15951619
authToken,
15961620
mcpMarketplaceEnabled,
1621+
telemetryOptIn,
15971622
} = await this.getState()
15981623

15991624
return {
@@ -1613,6 +1638,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
16131638
isLoggedIn: !!authToken,
16141639
userInfo,
16151640
mcpMarketplaceEnabled,
1641+
telemetryOptIn,
16161642
}
16171643
}
16181644

@@ -1791,6 +1817,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
17911817
.get("reasoningEffort", "medium")
17921818

17931819
const mcpMarketplaceEnabled = vscode.workspace.getConfiguration("cline").get<boolean>("mcpMarketplace.enabled", true)
1820+
const telemetryOptIn = vscode.workspace.getConfiguration("cline").get<boolean | null>("enableTelemetry", null)
17941821

17951822
return {
17961823
apiConfiguration: {
@@ -1847,6 +1874,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
18471874
previousModeModelId,
18481875
previousModeModelInfo,
18491876
mcpMarketplaceEnabled,
1877+
telemetryOptIn,
18501878
}
18511879
}
18521880

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { createClineAPI } from "./exports"
88
import "./utils/path" // necessary to have access to String.prototype.toPosix
99
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
1010
import assert from "node:assert"
11+
import { telemetryService } from "./services/telemetry/TelemetryService"
1112

1213
/*
1314
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -189,6 +190,7 @@ export function activate(context: vscode.ExtensionContext) {
189190

190191
// This method is called when your extension is deactivated
191192
export function deactivate() {
193+
telemetryService.shutdown()
192194
Logger.log("Cline extension deactivated")
193195
}
194196

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { PostHog } from "posthog-node"
2+
import * as vscode from "vscode"
3+
4+
class PostHogClient {
5+
private static instance: PostHogClient
6+
private client: PostHog
7+
private distinctId: string = vscode.env.machineId
8+
private telemetryEnabled: boolean = false
9+
10+
private constructor() {
11+
this.client = new PostHog("phc_qfOAGxZw2TL5O8p9KYd9ak3bPBFzfjC8fy5L6jNWY7K", {
12+
host: "https://us.i.posthog.com",
13+
enableExceptionAutocapture: false,
14+
})
15+
16+
// Initialize telemetry state based on user settings
17+
this.updateTelemetryState()
18+
19+
// Listen for settings changes
20+
vscode.workspace.onDidChangeConfiguration((e) => {
21+
if (e.affectsConfiguration("cline.enableTelemetry") || e.affectsConfiguration("telemetry.telemetryLevel")) {
22+
this.updateTelemetryState()
23+
}
24+
})
25+
}
26+
27+
private updateTelemetryState(): void {
28+
// First check global telemetry level - telemetry should only be enabled when level is "all"
29+
const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get<string>("telemetryLevel", "all")
30+
const globalTelemetryEnabled = telemetryLevel === "all"
31+
32+
// Only check Cline setting if global telemetry is enabled
33+
if (globalTelemetryEnabled) {
34+
const clineOptIn = vscode.workspace.getConfiguration("cline").get<boolean | null>("enableTelemetry", null)
35+
this.telemetryEnabled = clineOptIn === true
36+
}
37+
38+
// Update PostHog client state based on telemetry preference
39+
if (this.telemetryEnabled) {
40+
this.client.optIn()
41+
// console.log("Telemetry enabled")
42+
} else {
43+
this.client.optOut()
44+
// console.log("Telemetry disabled")
45+
}
46+
}
47+
48+
public static getInstance(): PostHogClient {
49+
if (!PostHogClient.instance) {
50+
PostHogClient.instance = new PostHogClient()
51+
}
52+
return PostHogClient.instance
53+
}
54+
55+
public capture(event: { event: string; properties?: any }): void {
56+
// Only send events if telemetry is enabled
57+
if (this.telemetryEnabled) {
58+
this.client.capture({ distinctId: this.distinctId, event: event.event, properties: event.properties })
59+
// console.log("Captured event", { distinctId: this.distinctId, event: event.event, properties: event.properties })
60+
}
61+
}
62+
63+
public isTelemetryEnabled(): boolean {
64+
return this.telemetryEnabled
65+
}
66+
67+
public async shutdown(): Promise<void> {
68+
await this.client.shutdown()
69+
}
70+
}
71+
72+
// Export a single instance
73+
export const telemetryService = PostHogClient.getInstance()

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface ExtensionState {
8181
photoURL: string | null
8282
}
8383
mcpMarketplaceEnabled?: boolean
84+
telemetryOptIn: boolean | null
8485
}
8586

8687
export interface ClineMessage {

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export interface WebviewMessage {
5050
| "searchCommits"
5151
| "showMcpView"
5252
| "fetchLatestMcpServersFromHub"
53+
| "telemetryOptIn"
54+
| "openTelemetrySettings"
5355
// | "relaunchChromeDebugMode"
5456
text?: string
5557
disabled?: boolean

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import BrowserSessionRow from "./BrowserSessionRow"
2626
import ChatRow from "./ChatRow"
2727
import ChatTextArea from "./ChatTextArea"
2828
import TaskHeader from "./TaskHeader"
29+
import TelemetryBanner from "../common/TelemetryBanner"
2930

3031
interface ChatViewProps {
3132
isHidden: boolean
@@ -37,7 +38,7 @@ interface ChatViewProps {
3738
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
3839

3940
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
40-
const { version, clineMessages: messages, taskHistory, apiConfiguration } = useExtensionState()
41+
const { version, clineMessages: messages, taskHistory, apiConfiguration, telemetryOptIn } = useExtensionState()
4142

4243
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
4344
const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
@@ -789,6 +790,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
789790
flexDirection: "column",
790791
paddingBottom: "10px",
791792
}}>
793+
{telemetryOptIn === null && <TelemetryBanner />}
794+
792795
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
793796
<div style={{ padding: "0 20px", flexShrink: 0 }}>
794797
<h2>What can I do for you?</h2>

0 commit comments

Comments
 (0)