From 06453d4c96d36d5b5f8788d10452c42c2199f230 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Tue, 26 May 2026 08:45:12 +0100 Subject: [PATCH 1/3] feat(analytics): tag discuss-launched task events with signal_report_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a user starts a chat from an inbox report via the Discuss button, the task and task run already record the report ID on the backend, but the analytics events did not — so it was not possible to filter PostHog events down to a single discuss-launched session. This adds `signal_report_id` to: - `TASK_CREATED` (both call sites: useDiscussReport and useTaskCreation), so the event that opens the session is directly tagged. - All subsequent events fired while the user is viewing the task, via a PostHog super-property registered through `setActiveTaskAnalyticsContext` on navigation. The property is set when entering a task-detail view whose task has `signal_report` populated, and cleared when navigating elsewhere (including via goBack/goForward). Caveat: background events for non-active tasks (e.g. `AGENT_FILE_ACTIVITY` firing while the user is on the inbox) won't pick up the super-property. Threading the report ID through SessionService directly would cover that, but it's substantially more invasive and can be done later if needed. Generated-By: PostHog Code Task-Id: d9940e39-a843-4018-ace7-be597fd964cd --- .../features/inbox/hooks/useDiscussReport.ts | 1 + .../task-detail/hooks/useTaskCreation.ts | 1 + .../renderer/stores/navigationStore.test.ts | 5 ++++- .../src/renderer/stores/navigationStore.ts | 17 ++++++++++++--- apps/code/src/renderer/utils/analytics.ts | 21 +++++++++++++++++++ apps/code/src/shared/types/analytics.ts | 1 + 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/apps/code/src/renderer/features/inbox/hooks/useDiscussReport.ts b/apps/code/src/renderer/features/inbox/hooks/useDiscussReport.ts index b81a1e28c..4b31b0d6d 100644 --- a/apps/code/src/renderer/features/inbox/hooks/useDiscussReport.ts +++ b/apps/code/src/renderer/features/inbox/hooks/useDiscussReport.ts @@ -159,6 +159,7 @@ export function useDiscussReport({ has_branch: false, cloud_run_source: "signal_report", cloud_pr_authorship_mode: "user", + signal_report_id: reportId, adapter, }); } else { diff --git a/apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts b/apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts index 3d4fe4d7c..8b553ab95 100644 --- a/apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts +++ b/apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts @@ -149,6 +149,7 @@ async function trackTaskCreated( : undefined, cloud_pr_authorship_mode: workspaceMode === "cloud" ? input.cloudPrAuthorshipMode : undefined, + signal_report_id: input.signalReportId, uses_worktree_link: usesWorktreeLink, uses_worktree_include: usesWorktreeInclude, adapter: input.adapter, diff --git a/apps/code/src/renderer/stores/navigationStore.test.ts b/apps/code/src/renderer/stores/navigationStore.test.ts index c5fa8232c..f1773a568 100644 --- a/apps/code/src/renderer/stores/navigationStore.test.ts +++ b/apps/code/src/renderer/stores/navigationStore.test.ts @@ -16,7 +16,10 @@ vi.mock("@renderer/trpc/client", () => ({ }, })); -vi.mock("@utils/analytics", () => ({ track: vi.fn() })); +vi.mock("@utils/analytics", () => ({ + track: vi.fn(), + setActiveTaskAnalyticsContext: vi.fn(), +})); vi.mock("@utils/logger", () => ({ logger: { scope: () => ({ info: vi.fn(), error: vi.fn(), debug: vi.fn() }) }, })); diff --git a/apps/code/src/renderer/stores/navigationStore.ts b/apps/code/src/renderer/stores/navigationStore.ts index 06ad8a56e..3bfb98fb3 100644 --- a/apps/code/src/renderer/stores/navigationStore.ts +++ b/apps/code/src/renderer/stores/navigationStore.ts @@ -3,7 +3,7 @@ import { workspaceApi } from "@features/workspace/hooks/useWorkspace"; import { getTaskDirectory } from "@hooks/useRepositoryDirectory"; import type { Task } from "@shared/types"; import { ANALYTICS_EVENTS } from "@shared/types/analytics"; -import { track } from "@utils/analytics"; +import { setActiveTaskAnalyticsContext, track } from "@utils/analytics"; import { electronStorage } from "@utils/electronStorage"; import { logger } from "@utils/logger"; import { getTaskRepository } from "@utils/repository"; @@ -132,6 +132,9 @@ export const useNavigationStore = create()( history: newHistory, historyIndex: newHistory.length - 1, }); + setActiveTaskAnalyticsContext( + newView.type === "task-detail" ? (newView.data ?? null) : null, + ); }; return { @@ -306,10 +309,14 @@ export const useNavigationStore = create()( const { history, historyIndex } = get(); if (historyIndex > 0) { const newIndex = historyIndex - 1; + const newView = history[newIndex]; set({ - view: history[newIndex], + view: newView, historyIndex: newIndex, }); + setActiveTaskAnalyticsContext( + newView.type === "task-detail" ? (newView.data ?? null) : null, + ); } }, @@ -317,10 +324,14 @@ export const useNavigationStore = create()( const { history, historyIndex } = get(); if (historyIndex < history.length - 1) { const newIndex = historyIndex + 1; + const newView = history[newIndex]; set({ - view: history[newIndex], + view: newView, historyIndex: newIndex, }); + setActiveTaskAnalyticsContext( + newView.type === "task-detail" ? (newView.data ?? null) : null, + ); } }, diff --git a/apps/code/src/renderer/utils/analytics.ts b/apps/code/src/renderer/utils/analytics.ts index 9ea3330d4..b38ddfe6a 100644 --- a/apps/code/src/renderer/utils/analytics.ts +++ b/apps/code/src/renderer/utils/analytics.ts @@ -4,6 +4,7 @@ import posthog from "posthog-js/dist/module.full.no-external"; // 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 { Task } from "@shared/types"; import type { EventPropertyMap, UserIdentifyProperties, @@ -148,6 +149,26 @@ export function resetUser() { posthog.reset(); } +/** + * Attach (or clear) task-scoped super-properties so every subsequent event + * carries the context of the currently active task. Pass `null` when no task + * is active (e.g. when navigating to a non-task view) to clear the context. + * + * Currently used to tag every event fired while the user is inside a task + * launched from an inbox report via the Discuss button. + */ +export function setActiveTaskAnalyticsContext(task: Task | null) { + if (!isInitialized) { + return; + } + + if (task?.signal_report) { + posthog.register({ signal_report_id: task.signal_report }); + } else { + posthog.unregister("signal_report_id"); + } +} + export function track( eventName: K, ...args: EventPropertyMap[K] extends never diff --git a/apps/code/src/shared/types/analytics.ts b/apps/code/src/shared/types/analytics.ts index 61f56c0cf..395d89d27 100644 --- a/apps/code/src/shared/types/analytics.ts +++ b/apps/code/src/shared/types/analytics.ts @@ -61,6 +61,7 @@ export interface TaskCreateProperties { has_sandbox_environment?: boolean; cloud_run_source?: "manual" | "signal_report"; cloud_pr_authorship_mode?: "user" | "bot"; + signal_report_id?: string; /** Worktree mode: repo has a non-empty .worktreelink file */ uses_worktree_link?: boolean; /** Worktree mode: repo has a non-empty .worktreeinclude file */ From a163281afc4721dc759386ea55ea24734f1fe739 Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Tue, 26 May 2026 09:13:30 +0100 Subject: [PATCH 2/3] fix(analytics): clear signal_report_id super-property on init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostHog persists super-properties to local storage, so `signal_report_id` registered by `setActiveTaskAnalyticsContext` during one session would survive an app restart. Events fired on next launch — before navigation re-applies the current task's context — could carry a stale ID from the previous session. Unregister `signal_report_id` inside `initializePostHog` so every cold start begins with a clean slate. The helper still re-registers it when the user lands on a discuss-launched task. Addresses Greptile P2 review feedback on #2369. Generated-By: PostHog Code Task-Id: d9940e39-a843-4018-ace7-be597fd964cd --- apps/code/src/renderer/utils/analytics.test.ts | 1 + apps/code/src/renderer/utils/analytics.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/apps/code/src/renderer/utils/analytics.test.ts b/apps/code/src/renderer/utils/analytics.test.ts index bf9f51289..3f42582b6 100644 --- a/apps/code/src/renderer/utils/analytics.test.ts +++ b/apps/code/src/renderer/utils/analytics.test.ts @@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mockPosthog = { init: vi.fn(), register: vi.fn(), + unregister: vi.fn(), onFeatureFlags: vi.fn(), isFeatureEnabled: vi.fn(), startSessionRecording: vi.fn(), diff --git a/apps/code/src/renderer/utils/analytics.ts b/apps/code/src/renderer/utils/analytics.ts index b38ddfe6a..ca1df48b5 100644 --- a/apps/code/src/renderer/utils/analytics.ts +++ b/apps/code/src/renderer/utils/analytics.ts @@ -51,6 +51,11 @@ export function initializePostHog() { }); posthog.register({ team: "posthog-code" }); + // Super-properties persist across app restarts via local storage. Clear + // any task-scoped properties from the previous session so events fired + // before navigation re-applies the current task's context are not + // mislabelled with stale values. + posthog.unregister("signal_report_id"); isInitialized = true; From 72b0df3703fb11f790c06f2fe20a8c148ffc016b Mon Sep 17 00:00:00 2001 From: Andrew Maguire Date: Wed, 27 May 2026 12:45:44 +0100 Subject: [PATCH 3/3] feat(analytics): forward signal_report_id to LLM gateway Thread the inbox report UUID into configureEnvironment so it is emitted as an x-posthog-property-signal_report_id header. The PostHog LLM gateway lifts it onto the $ai_generation events the agent produces, so a discuss- launched session's LLM traffic is tagged at source (Anthropic path only). Renderer events were already tagged via the super-property in #2369; this covers the agent/gateway-emitted events that the renderer cannot reach. --- .../agent-server.configure-environment.test.ts | 14 ++++++++++++++ packages/agent/src/server/agent-server.ts | 4 ++++ packages/agent/src/types.ts | 1 + 3 files changed, 19 insertions(+) diff --git a/packages/agent/src/server/agent-server.configure-environment.test.ts b/packages/agent/src/server/agent-server.configure-environment.test.ts index 23e3ed33d..c855de495 100644 --- a/packages/agent/src/server/agent-server.configure-environment.test.ts +++ b/packages/agent/src/server/agent-server.configure-environment.test.ts @@ -5,6 +5,7 @@ interface TestableServer { configureEnvironment(args?: { isInternal?: boolean; originProduct?: string | null; + signalReportId?: string | null; taskId?: string | null; taskRunId?: string | null; taskUserId?: number | null; @@ -124,6 +125,7 @@ describe("AgentServer.configureEnvironment", () => { buildServer("background").configureEnvironment({ isInternal: true, originProduct: "signal_report", + signalReportId: "report-123", taskId: "task-abc", taskRunId: "run-xyz", taskUserId: 42, @@ -133,6 +135,7 @@ describe("AgentServer.configureEnvironment", () => { [ "x-posthog-property-task_origin_product: signal_report", "x-posthog-property-task_internal: true", + "x-posthog-property-signal_report_id: report-123", "x-posthog-property-task_id: task-abc", "x-posthog-property-task_run_id: run-xyz", "x-posthog-property-task_user_id: 42", @@ -140,6 +143,17 @@ describe("AgentServer.configureEnvironment", () => { ); }); + it("omits signal_report_id from ANTHROPIC_CUSTOM_HEADERS for non-report tasks", () => { + buildServer("background").configureEnvironment({ + isInternal: false, + taskId: "task-abc", + }); + + expect(process.env.ANTHROPIC_CUSTOM_HEADERS).not.toContain( + "signal_report_id", + ); + }); + it("omits optional task metadata from ANTHROPIC_CUSTOM_HEADERS when not provided", () => { buildServer("background").configureEnvironment({ isInternal: false }); diff --git a/packages/agent/src/server/agent-server.ts b/packages/agent/src/server/agent-server.ts index 37ea4d8b8..332d86316 100644 --- a/packages/agent/src/server/agent-server.ts +++ b/packages/agent/src/server/agent-server.ts @@ -867,6 +867,7 @@ export class AgentServer { this.configureEnvironment({ isInternal: preTask?.internal === true, originProduct: preTask?.origin_product, + signalReportId: preTask?.signal_report, taskId: payload.task_id, taskRunId: payload.run_id, taskUserId: payload.user_id, @@ -1853,12 +1854,14 @@ ${signedCommitInstructions} private configureEnvironment({ isInternal = false, originProduct, + signalReportId, taskId, taskRunId, taskUserId, }: { isInternal?: boolean; originProduct?: string | null; + signalReportId?: string | null; taskId?: string | null; taskRunId?: string | null; taskUserId?: number | null; @@ -1877,6 +1880,7 @@ ${signedCommitInstructions} const customHeaders = buildGatewayPropertyHeaders({ task_origin_product: originProduct, task_internal: isInternal, + signal_report_id: signalReportId, task_id: taskId, task_run_id: taskRunId, task_user_id: taskUserId, diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts index 6047c3e9c..279f47804 100644 --- a/packages/agent/src/types.ts +++ b/packages/agent/src/types.ts @@ -39,6 +39,7 @@ export interface Task { | "support_queue" | "session_summaries" | "signal_report"; + signal_report?: string | null; // Inbox report UUID when origin_product is "signal_report" github_integration?: number | null; repository: string; // Format: "organization/repository" (e.g., "posthog/posthog-js") json_schema?: Record | null; // JSON schema for task output validation