From 7de6520cb7b0078947ea123e0c223125e41b9569 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 13:07:56 -0700 Subject: [PATCH 1/3] feat(posthog): correlate task events with Go logs via request_id Auto-injects server request_id into all PostHog server events from AsyncLocalStorage context. Adds task_request_started client event fired when SSE traceparent response header arrives, carrying the trace ID used by Go for log correlation. task_generation_aborted now includes the in-flight request_id when available. Co-Authored-By: Claude Opus 4.7 --- .../app/workspace/[workspaceId]/home/home.tsx | 10 +++++++ .../[workspaceId]/home/hooks/use-chat.ts | 29 +++++++++++++++++-- .../w/[workflowId]/components/panel/panel.tsx | 12 +++++++- apps/sim/lib/posthog/events.ts | 15 ++++++++++ apps/sim/lib/posthog/server.ts | 4 ++- 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index 8bd266a8724..b3c4cc48519 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -143,12 +143,21 @@ export function Home({ chatId }: HomeProps = {}) { editQueuedMessage, previewSession, genericResourceData, + getCurrentRequestId, } = useChat( workspaceId, chatId, getMothershipUseChatOptions({ onResourceEvent: handleResourceEvent, initialActiveResourceId: initialResourceId, + onRequestStarted: ({ requestId, userMessageId }) => { + captureEvent(posthogRef.current, 'task_request_started', { + workspace_id: workspaceId, + view: 'mothership', + request_id: requestId, + user_message_id: userMessageId, + }) + }, }) ) @@ -198,6 +207,7 @@ export function Home({ chatId }: HomeProps = {}) { captureEvent(posthogRef.current, 'task_generation_aborted', { workspace_id: workspaceId, view: 'mothership', + request_id: getCurrentRequestId(), }) void stopGeneration().catch(() => {}) } diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index 6d2c7527a6b..07d3fa5e4d7 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -167,6 +167,7 @@ export interface UseChatReturn { editQueuedMessage: (id: string) => QueuedMessage | undefined previewSession: FilePreviewSession | null genericResourceData: GenericResourceData | null + getCurrentRequestId: () => string | undefined } const DEPLOY_TOOL_NAMES: Set = new Set([ @@ -1278,6 +1279,13 @@ export interface UseChatOptions { onTitleUpdate?: () => void onStreamEnd?: (chatId: string, messages: ChatMessage[]) => void initialActiveResourceId?: string | null + /** + * Fired once per chat send as soon as the server's `traceparent` + * response header arrives (i.e. before any stream content). Used by + * callers to emit a follow-up PostHog event carrying the request ID + * for correlation with Go-side logs. + */ + onRequestStarted?: (info: { requestId: string; userMessageId: string }) => void } interface ActiveStreamRecovery { @@ -1293,7 +1301,10 @@ interface StopGenerationOptions { } export function getMothershipUseChatOptions( - options: Pick = {} + options: Pick< + UseChatOptions, + 'onResourceEvent' | 'onStreamEnd' | 'initialActiveResourceId' | 'onRequestStarted' + > = {} ): UseChatOptions { return { apiPath: MOTHERSHIP_CHAT_API_PATH, @@ -1305,7 +1316,7 @@ export function getMothershipUseChatOptions( export function getWorkflowCopilotUseChatOptions( options: Pick< UseChatOptions, - 'workflowId' | 'onToolResult' | 'onTitleUpdate' | 'onStreamEnd' + 'workflowId' | 'onToolResult' | 'onTitleUpdate' | 'onStreamEnd' | 'onRequestStarted' > = {} ): UseChatOptions { return { @@ -1351,6 +1362,10 @@ export function useChat( onTitleUpdateRef.current = options?.onTitleUpdate const onStreamEndRef = useRef(options?.onStreamEnd) onStreamEndRef.current = options?.onStreamEnd + const onRequestStartedRef = useRef(options?.onRequestStarted) + onRequestStartedRef.current = options?.onRequestStarted + + const getCurrentRequestId = useCallback(() => streamRequestIdRef.current, []) const clearQueueDispatchState = useCallback(() => { queueDispatchEpochRef.current++ @@ -4209,6 +4224,15 @@ export function useChat( if (traceparent) { streamTraceparentRef.current = traceparent setCurrentChatTraceparent(traceparent) + const parts = traceparent.split('-') + const traceId = parts.length === 4 ? parts[1] : '' + if (/^[0-9a-f]{32}$/.test(traceId) && traceId !== '0'.repeat(32)) { + try { + onRequestStartedRef.current?.({ requestId: traceId, userMessageId }) + } catch (callbackError) { + logger.warn('onRequestStarted callback threw', { error: callbackError }) + } + } } if (!response.ok) { @@ -5103,5 +5127,6 @@ export function useChat( editQueuedMessage, previewSession, genericResourceData, + getCurrentRequestId, } } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 64acc3d2a27..de69bbd886c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -346,6 +346,7 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel removeFromQueue: copilotRemoveFromQueue, sendNow: copilotSendNow, editQueuedMessage: copilotEditQueuedMessage, + getCurrentRequestId: getCopilotCurrentRequestId, } = useChat( workspaceId, copilotChatId, @@ -353,6 +354,14 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel workflowId: activeWorkflowId || undefined, onTitleUpdate: loadCopilotChats, onToolResult: handleCopilotToolResult, + onRequestStarted: ({ requestId, userMessageId }) => { + captureEvent(posthogRef.current, 'task_request_started', { + workspace_id: workspaceId, + view: 'copilot', + request_id: requestId, + user_message_id: userMessageId, + }) + }, }) ) @@ -413,9 +422,10 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel captureEvent(posthogRef.current, 'task_generation_aborted', { workspace_id: workspaceId, view: 'copilot', + request_id: getCopilotCurrentRequestId(), }) copilotStopGeneration() - }, [copilotStopGeneration, workspaceId]) + }, [copilotStopGeneration, getCopilotCurrentRequestId, workspaceId]) const handleCopilotSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { diff --git a/apps/sim/lib/posthog/events.ts b/apps/sim/lib/posthog/events.ts index 41f26124919..f95ded0e11a 100644 --- a/apps/sim/lib/posthog/events.ts +++ b/apps/sim/lib/posthog/events.ts @@ -418,6 +418,7 @@ export interface PostHogEventMap { task_generation_aborted: { workspace_id: string view: 'mothership' | 'copilot' + request_id?: string } task_message_sent: { @@ -427,6 +428,20 @@ export interface PostHogEventMap { is_new_task: boolean } + /** + * Fired when the server has assigned a request ID for a chat send (i.e. + * the SSE response headers have arrived with a `traceparent`). Pairs + * one-to-one with `task_message_sent` (or with a queued/replay dispatch) + * via timestamp proximity, and carries the `request_id` used by Go for + * log correlation. + */ + task_request_started: { + workspace_id: string + view: 'mothership' | 'copilot' + request_id: string + user_message_id: string + } + tour_started: { tour_type: 'nav' | 'workflow' } diff --git a/apps/sim/lib/posthog/server.ts b/apps/sim/lib/posthog/server.ts index 99a76a402cc..2e0229df10b 100644 --- a/apps/sim/lib/posthog/server.ts +++ b/apps/sim/lib/posthog/server.ts @@ -1,4 +1,4 @@ -import { createLogger } from '@sim/logger' +import { createLogger, getRequestContext } from '@sim/logger' import type { PostHog } from 'posthog-node' import type { PostHogEventMap, PostHogEventName } from '@/lib/posthog/events' @@ -71,10 +71,12 @@ export function captureServerEvent( const client = getClient() if (!client) return + const requestId = getRequestContext()?.requestId client.capture({ distinctId, event, properties: { + ...(requestId ? { request_id: requestId } : {}), ...properties, ...(options?.groups ? { $groups: options.groups } : {}), ...(options?.set ? { $set: options.set } : {}), From 116a3c554e4c191c0fec1870736e0bf573ec567d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 13:13:21 -0700 Subject: [PATCH 2/3] improvement(posthog): tighten trace ID parse and verbose comments --- .../workspace/[workspaceId]/home/hooks/use-chat.ts | 12 +++--------- apps/sim/lib/posthog/events.ts | 8 +------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index 07d3fa5e4d7..d72b45c45a5 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -1279,12 +1279,7 @@ export interface UseChatOptions { onTitleUpdate?: () => void onStreamEnd?: (chatId: string, messages: ChatMessage[]) => void initialActiveResourceId?: string | null - /** - * Fired once per chat send as soon as the server's `traceparent` - * response header arrives (i.e. before any stream content). Used by - * callers to emit a follow-up PostHog event carrying the request ID - * for correlation with Go-side logs. - */ + /** Fired when the server's `traceparent` response header arrives, before any stream content. */ onRequestStarted?: (info: { requestId: string; userMessageId: string }) => void } @@ -4224,9 +4219,8 @@ export function useChat( if (traceparent) { streamTraceparentRef.current = traceparent setCurrentChatTraceparent(traceparent) - const parts = traceparent.split('-') - const traceId = parts.length === 4 ? parts[1] : '' - if (/^[0-9a-f]{32}$/.test(traceId) && traceId !== '0'.repeat(32)) { + const traceId = traceparent.split('-')[1] ?? '' + if (/^[0-9a-f]{32}$/.test(traceId)) { try { onRequestStartedRef.current?.({ requestId: traceId, userMessageId }) } catch (callbackError) { diff --git a/apps/sim/lib/posthog/events.ts b/apps/sim/lib/posthog/events.ts index f95ded0e11a..7f928d7f70a 100644 --- a/apps/sim/lib/posthog/events.ts +++ b/apps/sim/lib/posthog/events.ts @@ -428,13 +428,7 @@ export interface PostHogEventMap { is_new_task: boolean } - /** - * Fired when the server has assigned a request ID for a chat send (i.e. - * the SSE response headers have arrived with a `traceparent`). Pairs - * one-to-one with `task_message_sent` (or with a queued/replay dispatch) - * via timestamp proximity, and carries the `request_id` used by Go for - * log correlation. - */ + /** Pairs with `task_message_sent` via `request_id` for correlation with server-side logs. */ task_request_started: { workspace_id: string view: 'mothership' | 'copilot' From 1ed9f52ff42d82ce2b9be39b944a8843c2adec49 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 13:16:05 -0700 Subject: [PATCH 3/3] fix(posthog): use traceparent header as canonical request_id source --- apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts | 5 ++++- apps/sim/lib/posthog/server.ts | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index d72b45c45a5..f320d44d46a 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -1360,7 +1360,10 @@ export function useChat( const onRequestStartedRef = useRef(options?.onRequestStarted) onRequestStartedRef.current = options?.onRequestStarted - const getCurrentRequestId = useCallback(() => streamRequestIdRef.current, []) + const getCurrentRequestId = useCallback(() => { + const traceId = streamTraceparentRef.current?.split('-')[1] ?? '' + return /^[0-9a-f]{32}$/.test(traceId) ? traceId : undefined + }, []) const clearQueueDispatchState = useCallback(() => { queueDispatchEpochRef.current++ diff --git a/apps/sim/lib/posthog/server.ts b/apps/sim/lib/posthog/server.ts index 2e0229df10b..c81349e2e79 100644 --- a/apps/sim/lib/posthog/server.ts +++ b/apps/sim/lib/posthog/server.ts @@ -71,13 +71,14 @@ export function captureServerEvent( const client = getClient() if (!client) return - const requestId = getRequestContext()?.requestId + const contextRequestId = getRequestContext()?.requestId + const props = properties as Record client.capture({ distinctId, event, properties: { - ...(requestId ? { request_id: requestId } : {}), ...properties, + ...(contextRequestId && !('request_id' in props) ? { request_id: contextRequestId } : {}), ...(options?.groups ? { $groups: options.groups } : {}), ...(options?.set ? { $set: options.set } : {}), ...(options?.setOnce ? { $set_once: options.setOnce } : {}),