diff --git a/apps/code/src/renderer/features/inbox/hooks/useDiscussReport.ts b/apps/code/src/renderer/features/inbox/hooks/useDiscussReport.ts index 7fdd66cde..56880a55b 100644 --- a/apps/code/src/renderer/features/inbox/hooks/useDiscussReport.ts +++ b/apps/code/src/renderer/features/inbox/hooks/useDiscussReport.ts @@ -133,6 +133,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.test.ts b/apps/code/src/renderer/utils/analytics.test.ts index 3a56c8edd..c0a329f03 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 b4b2414ce..d17665203 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, @@ -62,6 +63,9 @@ export function initializePostHog() { }, }); + // Clear stale task-scoped super-properties from the previous session. + posthog.unregister("signal_report_id"); + isInitialized = true; registerPersistentSuperProperties(); @@ -175,6 +179,26 @@ export function resetUser() { registerPersistentSuperProperties(); } +/** + * 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 58d12542e..dc5dfed4a 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 */ 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