feat(telemetry): instrument renderer failure and CTA events#2360
Conversation
Add PostHog instrumentation for app failures and user CTAs: - daemon_failure (machine-readable code on DaemonStatus, IPC-forwarded) - api_error central interceptor (categorized, IDs stripped) - terminal_attach_failed (pane error + open timeout) - CTA triads: task_create, session_kill, settings_save, orchestrator_spawn (board/restore_dialog), notification open/read All events sanitized: project_id hashed, enum-only codes, raw error messages never sent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…stops, spawn triad - normalizeApiOperation now matches OpenAPI route templates instead of a segment heuristic, so static child routes (read-all, cleanup) stay intact and IDs are stripped for resources the heuristic missed (orchestrators, prs) - intentional daemon stops report state "stopped" instead of a coded exit, so they no longer count as daemon_failure - centralize the orchestrator_spawn requested/succeeded/failed triad inside spawnOrchestrator, keyed by source; whitelist board/restore_dialog/topbar/ sidebar/project_add in the telemetry sanitizer Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-cta-telemetry # Conflicts: # frontend/src/renderer/components/NewTaskDialog.tsx # frontend/src/renderer/components/ProjectSettingsForm.tsx # frontend/src/renderer/lib/api-client.test.ts # frontend/src/renderer/lib/spawn-orchestrator.test.ts # frontend/src/renderer/lib/spawn-orchestrator.ts
Code Review —
|
| Area | Covered |
|---|---|
normalizeApiOperation (ID stripping, static routes, heuristic gaps) |
✅ 4 cases |
| API error categories (5xx, 4xx, network, abort, daemon_unavailable) | ✅ |
| API error dedupe + window expiry | ✅ |
| Daemon failure report + dedupe + recovery reset | ✅ |
| Spawn triad (requested/succeeded/failed) + rethrow | ✅ |
| Sanitizer: every new event, enum enforcement, text dropping | ✅ |
| All 7 spawn sources in allowlist | ✅ |
No gaps found. Test plan claims 396 passing — the new tests are well-targeted.
Reviewed by AO Bot
Closes #2362
Summary
Adds PostHog instrumentation for two blind spots in the desktop app: failures (things breaking) and CTAs (what users actually do). All events flow through the existing
captureRendererEvent→sanitizeRendererPropertiesallowlist, so nothing leaves the renderer un-sanitized.Failure signals
ao.renderer.daemon_failure— a machine-readablecodeis now stamped onDaemonStatusat every main-process failure site (not_configured,daemon_unreachable,binary_missing,spawn_failed,exited,port_unconfirmed,not_ready,identity_mismatch). A small renderer subscriber (daemon-telemetry.ts) rides the existingdaemon:statusIPC push and reports the coarse fields (daemon_state,code,exit_code,signal) — never the raw message.ao.renderer.api_error— central interceptor inapi-client.tscategorizes every failed daemon call (daemon_unavailable/network_error/http_4xx/http_5xx), with the operation normalized toMETHOD /api/v1/resource/:id(IDs stripped) and a 30s dedupe window. Caller-initiated aborts are ignored.ao.renderer.terminal_attach_failed— reason enum (pane_error/open_timeout).CTA triads (requested → succeeded → failed)
task_create,session_kill,settings_saveorchestrator_spawnwith asourceenum (board/restore_dialog)notification_opened(target: pr/session) andnotification_marked_read(scope: single/all)Privacy
Every new event is added to the
sanitizeRendererPropertiesswitch.project_idis hashed toproject_id_hash(SHA-256); all free-text (titles, error messages, paths, arbitrarysource/target/reasonvalues) is dropped by the enum-only allowlist. Covered by unit tests.Telemetry verification (live PostHog, project 475752)
Every failure and CTA path was exercised end-to-end against a running packaged instance and confirmed to land with the sanitized shape. Counts below are from this verification session.
orchestrator_spawn_requestedsource: board,project_id_hashorchestrator_spawn_succeededsource: board,project_id_hashtask_create_requestedproject_id_hashtask_create_succeededproject_id_hashtask_create_failedproject_id_hash(raw title/brief dropped)session_kill_requestedproject_id_hashsession_kill_failedproject_id_hashsettings_save_requestedproject_id_hashsettings_save_succeededproject_id_hashsettings_save_failedproject_id_hashapi_errorerror_category: daemon_unavailable | network_error,status: 503, normalizedoperationterminal_attach_failedreason: open_timeoutdaemon_failuredaemon_state/code/signal); raw message droppednotification_opened/notification_marked_readtelemetry.test.ts(not driven live this session)Sanitization held on every event:
project_id→ SHA-256project_id_hash, and notitle/brief/error/message/bodyleaked — only enum values survived the allowlist. Common envelope (surface,build_mode,platform,app_version) is attached by the base capture.Test plan
npm run typecheck— cleannpm run test— 396 passing (new: daemon-telemetry subscriber, api_error interceptor + normalizeApiOperation, sanitizer allowlist for every new event; updated two daemon-attach exact-match assertions for the newcodefield)npx prettier --checkon all touched files