Error Tracking & Telemetry
PostHog-based error capture, analytics events, and feedback pipeline across main and renderer processes
Cosmo uses PostHog for error tracking, analytics, and AI-collected feedback. Instrumentation spans both Electron processes and is silently disabled in local development.
Source files:
packages/desktop/src/main/error-tracker.ts— main processpackages/desktop/src/renderer/src/lib/posthog.ts— renderer processpackages/desktop/src/renderer/src/components/ErrorBoundary.tsx— React error boundary
Activation
PostHog is only active when the API key is present. The host variable is optional (defaults to https://us.i.posthog.com):
| Env var | Description |
|---|---|
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN | Project API key |
VITE_PUBLIC_POSTHOG_HOST | Ingest host (default: https://us.i.posthog.com) |
In local development these are not set, so all PostHog calls are no-ops. No analytics data is sent during development.
Main Process (error-tracker.ts)
The main process uses posthog-node (server-side SDK). It is initialised once at app startup by initMainProcessErrorTracking(), which is called from packages/desktop/src/main/index.ts.
Initialisation
initMainProcessErrorTracking()Creates a PostHog client with:
| Option | Value |
|---|---|
flushAt | 5 events |
flushInterval | 5 000 ms |
Error Capture
Two Node.js process events are hooked:
| Event | Trigger |
|---|---|
uncaughtException | Synchronous exception that escaped all catch blocks |
unhandledRejection | Promise rejection with no .catch() handler |
Both call captureMainProcessError(error, source), which sends a $exception event to PostHog with these properties:
{
platform: 'desktop',
process: 'main',
source: 'uncaughtException' | 'unhandledRejection',
}Shutdown
await shutdownErrorTracker()Called on app.before-quit to flush any buffered events before the process exits.
Renderer Process (lib/posthog.ts)
The renderer uses posthog-js (browser SDK). It is initialised once by initPostHog(), called from packages/desktop/src/renderer/src/main.tsx during app bootstrap.
Initialisation Options
posthog.init(token, {
api_host: host,
capture_pageview: false, // Electron has no URL routing; pages are tracked manually
persistence: 'localStorage',
})
posthog.register({ platform: 'desktop' }) // super-property on every eventcapture_pageview: false prevents PostHog from auto-firing a pageview event when the Electron renderer loads — there is no meaningful URL to track.
The platform: 'desktop' super-property is registered globally so every event carries it for segmentation.
React Error Boundary (ErrorBoundary.tsx)
ErrorBoundary is a React class component wrapping the entire renderer tree. It catches unhandled render errors that escape component try/catch blocks.
Lifecycle
- A render error throws inside any descendant component.
- React calls
getDerivedStateFromError→ setshasError: true. - React calls
componentDidCatch(error, info)→ sends to PostHog:
posthog.captureException(error, {
platform: 'desktop',
componentStack: info.componentStack,
})- The boundary renders a fallback UI: a "Something went wrong" message with a Try again button that resets
hasErrorto false and re-mounts the children.
Theme Analytics Events
ThemeContext emits two events when the user changes appearance settings:
| Event | Properties | Trigger |
|---|---|---|
theme_mode_changed | { mode: 'dark' | 'light' } | User toggles dark/light mode |
theme_preset_changed | { preset: string } | User selects a colour preset |
Theme state is also registered as a super-property so all events carry the current theme:
posthog.register({ theme_mode: theme, theme_preset: preset })Feedback Pipeline
AI-collected feedback (from the cosmo_feedback_submit LLM tool) travels from the main process to the renderer for PostHog capture. This routing exists because PostHog survey events must come from the browser SDK to appear in the PostHog Surveys dashboard.
Flow
LLM calls cosmo_feedback_submit
↓
ChatManager (main process) captures feedback input
↓
IPC: posthog:capture-feedback event → renderer
↓
posthog.ts setupFeedbackCapture() listener
↓
PostHog "survey sent" event(s)Survey Event Structure
Each feedback submission produces up to two PostHog survey sent events:
Rating event (emitted only when importanceRating is provided):
posthog.capture('survey sent', {
$survey_id: surveyId,
[`$survey_response_${ratingQuestionId}`]: input.importanceRating,
$survey_submission_id: submissionId,
$survey_completed: false,
feedback_type: input.type,
feedback_title: input.title,
capability_area: input.capabilityArea,
})Text event (always emitted):
posthog.capture('survey sent', {
$survey_id: surveyId,
[`$survey_response_${textQuestionId}`]: responseText,
$survey_submission_id: submissionId,
$survey_completed: true,
feedback_type: input.type,
feedback_title: input.title,
feedback_description: input.description,
feedback_context: input.conversationContext,
capability_area: input.capabilityArea,
importance_rating: input.importanceRating,
})responseText is assembled from [type] title + description + context + userNotes, joined with double newlines.
FeedbackCaptureData Type
interface FeedbackCaptureData {
surveyId: string
ratingQuestionId: string
textQuestionId: string
input: {
type: string
title: string
description: string
conversationContext?: string
capabilityArea?: string
importanceRating?: number
userNotes?: string
}
}Data Sanitization
No explicit PII scrubbing is performed. The following practices limit exposure:
conversationContextis an optional field — ChatManager populates it only with a short summary, not the full message history.- Error stack traces from
componentDidCatchandcaptureMainProcessErrormay contain file paths but not user data. - The feedback text fields contain only what the user typed or what the AI extracted from the conversation — they never include authentication tokens or credentials.
Summary of All Tracked Events
| Event | Source | When |
|---|---|---|
$exception (main) | error-tracker.ts | Uncaught exception or unhandled rejection in main process |
$exception (renderer) | ErrorBoundary.tsx | Unhandled render error in React tree |
theme_mode_changed | ThemeContext.tsx | User toggles dark/light mode |
theme_preset_changed | ThemeContext.tsx | User changes colour preset |
survey sent | posthog.ts | AI feedback submitted via cosmo_feedback_submit tool |