From 292f47436945fc0ea86de854a27c89676d0833b6 Mon Sep 17 00:00:00 2001 From: Adam Bowker Date: Fri, 13 Mar 2026 14:01:10 -0700 Subject: [PATCH] chore(code): add permissions-related events --- .../features/sessions/service/service.test.ts | 5 ++- .../features/sessions/service/service.ts | 25 +++++++++++- apps/code/src/renderer/utils/analytics.ts | 24 ++++++++++++ apps/code/src/shared/types/analytics.ts | 38 +++++++++++++++++++ .../claude/permissions/permission-handlers.ts | 2 +- 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/apps/code/src/renderer/features/sessions/service/service.test.ts b/apps/code/src/renderer/features/sessions/service/service.test.ts index 0473981ef9..881e5e3f60 100644 --- a/apps/code/src/renderer/features/sessions/service/service.test.ts +++ b/apps/code/src/renderer/features/sessions/service/service.test.ts @@ -137,7 +137,10 @@ vi.mock("@features/sidebar/hooks/useTaskViewed", () => ({ }, })); -vi.mock("@utils/analytics", () => ({ track: vi.fn() })); +vi.mock("@utils/analytics", () => ({ + track: vi.fn(), + buildPermissionToolMetadata: vi.fn(() => ({})), +})); vi.mock("@utils/logger", () => ({ logger: { scope: () => ({ diff --git a/apps/code/src/renderer/features/sessions/service/service.ts b/apps/code/src/renderer/features/sessions/service/service.ts index fb1da790e7..82b29c84cd 100644 --- a/apps/code/src/renderer/features/sessions/service/service.ts +++ b/apps/code/src/renderer/features/sessions/service/service.ts @@ -40,7 +40,7 @@ import { import { ANALYTICS_EVENTS } from "@shared/types/analytics"; import type { AcpMessage, StoredLogEntry } from "@shared/types/session-events"; import { isJsonRpcRequest } from "@shared/types/session-events"; -import { track } from "@utils/analytics"; +import { buildPermissionToolMetadata, track } from "@utils/analytics"; import { logger } from "@utils/logger"; import { notifyPermissionRequest, @@ -571,6 +571,8 @@ export class SessionService { track(ANALYTICS_EVENTS.TASK_RUN_STARTED, { task_id: taskId, execution_type: "local", + initial_mode: executionMode, + adapter, }); const preferredModel = model ?? DEFAULT_GATEWAY_MODEL; @@ -1493,6 +1495,12 @@ export class SessionService { return; } + const permission = session.pendingPermissions.get(toolCallId); + track(ANALYTICS_EVENTS.PERMISSION_RESPONDED, { + task_id: taskId, + ...buildPermissionToolMetadata(permission, optionId, customInput), + }); + this.resolvePermission(session, toolCallId); try { @@ -1530,6 +1538,12 @@ export class SessionService { return; } + const permission = session.pendingPermissions.get(toolCallId); + track(ANALYTICS_EVENTS.PERMISSION_CANCELLED, { + task_id: taskId, + ...buildPermissionToolMetadata(permission), + }); + this.resolvePermission(session, toolCallId); try { @@ -1636,6 +1650,15 @@ export class SessionService { return; } + if (configOption.currentValue !== value) { + track(ANALYTICS_EVENTS.SESSION_CONFIG_CHANGED, { + task_id: taskId, + category, + from_value: configOption.currentValue, + to_value: value, + }); + } + await this.setSessionConfigOption(taskId, configOption.id, value); } diff --git a/apps/code/src/renderer/utils/analytics.ts b/apps/code/src/renderer/utils/analytics.ts index b2275b51c4..61361256e7 100644 --- a/apps/code/src/renderer/utils/analytics.ts +++ b/apps/code/src/renderer/utils/analytics.ts @@ -3,6 +3,7 @@ import posthog from "posthog-js/dist/module.full.no-external"; // The module.full.no-external bundle includes rrweb but not the initSessionRecording function // posthog-recorder (vs lazy-recorder) ensures recording is ready immediately import "posthog-js/dist/posthog-recorder"; +import type { PermissionRequest } from "@renderer/features/sessions/utils/parseSessionLogs"; import type { EventPropertyMap, UserIdentifyProperties, @@ -128,6 +129,29 @@ export function track( posthog.capture(eventName, args[0]); } +/** + * Build tool metadata for analytics on permission requests + */ +export function buildPermissionToolMetadata( + permission?: PermissionRequest, + selectedOptionId?: string, + customInput?: string, +): Record { + const selectedOption = permission?.options?.find( + (o) => o.optionId === selectedOptionId, + ); + const rawInput = permission?.toolCall?.rawInput as + | Record + | undefined; + + return { + tool_name: rawInput?.toolName, + option_id: selectedOptionId, + option_kind: selectedOption?.kind ?? "unknown", + custom_input: customInput, + }; +} + /** * Capture an exception for error tracking using PostHog's built-in exception tracking. */ diff --git a/apps/code/src/shared/types/analytics.ts b/apps/code/src/shared/types/analytics.ts index d374f061ee..526ae1dfe0 100644 --- a/apps/code/src/shared/types/analytics.ts +++ b/apps/code/src/shared/types/analytics.ts @@ -64,6 +64,8 @@ export interface TaskRunStartedProperties { task_id: string; execution_type: ExecutionType; model?: string; + initial_mode?: string; + adapter?: string; } export interface TaskRunCompletedProperties { @@ -151,6 +153,28 @@ export interface AgentSessionErrorProperties { error_type: string; } +// Permission events +export interface PermissionRespondedProperties { + task_id: string; + tool_name?: string; + option_id?: string; + option_kind?: string; + custom_input?: string; +} + +export interface PermissionCancelledProperties { + task_id: string; + tool_name?: string; +} + +// Session config events +export interface SessionConfigChangedProperties { + task_id: string; + category: string; + from_value: string; + to_value: string; +} + // Feedback events export interface TaskFeedbackProperties { task_id: string; @@ -203,6 +227,13 @@ export const ANALYTICS_EVENTS = { COMMAND_MENU_ACTION: "Command menu action", COMMAND_CENTER_VIEWED: "Command center viewed", + // Permission events + PERMISSION_RESPONDED: "Permission responded", + PERMISSION_CANCELLED: "Permission cancelled", + + // Session config events + SESSION_CONFIG_CHANGED: "Session config changed", + // Settings events SETTING_CHANGED: "Setting changed", @@ -249,6 +280,13 @@ export type EventPropertyMap = { [ANALYTICS_EVENTS.COMMAND_MENU_ACTION]: CommandMenuActionProperties; [ANALYTICS_EVENTS.COMMAND_CENTER_VIEWED]: never; + // Permission events + [ANALYTICS_EVENTS.PERMISSION_RESPONDED]: PermissionRespondedProperties; + [ANALYTICS_EVENTS.PERMISSION_CANCELLED]: PermissionCancelledProperties; + + // Session config events + [ANALYTICS_EVENTS.SESSION_CONFIG_CHANGED]: SessionConfigChangedProperties; + // Settings events [ANALYTICS_EVENTS.SETTING_CHANGED]: SettingChangedProperties; diff --git a/packages/agent/src/adapters/claude/permissions/permission-handlers.ts b/packages/agent/src/adapters/claude/permissions/permission-handlers.ts index 7279aa2e79..a35b68103a 100644 --- a/packages/agent/src/adapters/claude/permissions/permission-handlers.ts +++ b/packages/agent/src/adapters/claude/permissions/permission-handlers.ts @@ -356,7 +356,7 @@ async function handleDefaultPermissionFlow( kind: toolInfo.kind, content: toolInfo.content, locations: toolInfo.locations, - rawInput: toolInput as Record, + rawInput: { ...(toolInput as Record), toolName }, }, });