Skip to content

feat(telemetry): instrument renderer failure and CTA events#2360

Merged
illegalcall merged 3 commits into
AgentWrapper:mainfrom
Pritom14:feat/renderer-failure-cta-telemetry
Jul 5, 2026
Merged

feat(telemetry): instrument renderer failure and CTA events#2360
illegalcall merged 3 commits into
AgentWrapper:mainfrom
Pritom14:feat/renderer-failure-cta-telemetry

Conversation

@Pritom14

@Pritom14 Pritom14 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

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 captureRendererEventsanitizeRendererProperties allowlist, so nothing leaves the renderer un-sanitized.

Failure signals

  • ao.renderer.daemon_failure — a machine-readable code is now stamped on DaemonStatus at 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 existing daemon:status IPC push and reports the coarse fields (daemon_state, code, exit_code, signal) — never the raw message.
  • ao.renderer.api_error — central interceptor in api-client.ts categorizes every failed daemon call (daemon_unavailable / network_error / http_4xx / http_5xx), with the operation normalized to METHOD /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_save
  • orchestrator_spawn with a source enum (board / restore_dialog)
  • notification_opened (target: pr/session) and notification_marked_read (scope: single/all)

Privacy

Every new event is added to the sanitizeRendererProperties switch. project_id is hashed to project_id_hash (SHA-256); all free-text (titles, error messages, paths, arbitrary source/target/reason values) 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.

Event Fired Verified properties
orchestrator_spawn_requested source: board, project_id_hash
orchestrator_spawn_succeeded source: board, project_id_hash
task_create_requested project_id_hash
task_create_succeeded project_id_hash
task_create_failed project_id_hash (raw title/brief dropped)
session_kill_requested project_id_hash
session_kill_failed project_id_hash
settings_save_requested project_id_hash
settings_save_succeeded project_id_hash
settings_save_failed project_id_hash
api_error error_category: daemon_unavailable | network_error, status: 503, normalized operation
terminal_attach_failed reason: open_timeout
daemon_failure coarse fields only (daemon_state / code / signal); raw message dropped
notification_opened / notification_marked_read unit-tested enum-only allowlist verified in telemetry.test.ts (not driven live this session)

Sanitization held on every event: project_id → SHA-256 project_id_hash, and no title / brief / error / message / body leaked — 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 — clean
  • npm 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 new code field)
  • npx prettier --check on all touched files
  • E2E smoke of failure + CTA paths against a live instance, verified in PostHog project 475752 (see table above)

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>
Comment thread frontend/src/renderer/lib/api-client.ts
Comment thread frontend/src/main.ts

@illegalcall illegalcall left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments

Comment thread frontend/src/renderer/lib/api-client.ts Outdated
Comment thread frontend/src/renderer/lib/telemetry.ts Outdated
Comment thread frontend/src/main.ts
Pritom14 and others added 2 commits July 4, 2026 12:47
…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
@i-trytoohard

Copy link
Copy Markdown
Contributor

Code Review — feat(telemetry): instrument renderer failure and CTA events

Verdict: Looks good. No blocking issues. A few non-blocking maintenance notes below.

This is clean work. The privacy model is sound, the architecture is well-separated, and the test coverage is genuinely thorough — not just happy paths, but privacy boundaries, dedupe windows, AbortError exclusion, and recovery-reset semantics.


✅ What's strong

  • Failure code stamping in main process → renderer IPC relay. Main has no PostHog client; stamping a machine-readable code on DaemonStatus and having daemon-telemetry.ts ride the existing daemon:status push is the right separation. No new IPC channels.
  • Sanitizer allowlist is enum-only. Every new event drops free-text (message, title, brief, error, body) and only survives typed enum values. project_id → SHA-256 hash. The tests explicitly assert that raw paths/user text doesn't leak.
  • The exit handler early-return (main.ts) is a correct fix: intentional stops now get a clean { state: "stopped" } without a code, so they aren't counted as failures. Previously every exit — including user-initiated — would have been reported once telemetry landed.
  • API error dedupe is per-(operation, category, status) with a 30s window. A daemon outage making every polling query fail won't storm PostHog. AbortError (unmount cancels) correctly excluded.
  • All 7 spawn call sites pass the correct source, all 7 sources are in the sanitizer allowlist, and all 7 are tested. No orphaned or missing sources.

💡 Non-blocking notes

1. ORCHESTRATOR_SPAWN_SOURCES / OrchestratorSpawnSource drift (silent data loss risk)
telemetry.ts keeps a literal Set of allowed sources, duplicated from the type in spawn-orchestrator.ts (comment explains: avoid import cycle). If someone adds an 8th source to the type and uses it, the sanitizer silently drops source — no error, no test failure, the event just lacks the field. The existing test only iterates the 7 known values so it wouldn't catch a new one. Consider a dev-time assertion (e.g. satisfies) or a test that the set is exhaustive against a keyof-derived list. Not blocking — the comment flags it — but it's the weakest maintenance seam.

2. ROUTE_TEMPLATES is a hand-maintained copy of the OpenAPI schema
The comment acknowledges this ("Keep in sync with schema.ts"). The fallbackNormalize heuristic covers the common cases (segments after projects/sessions/notifications/orchestrators/prs), so a missed template degrades gracefully — IDs still get collapsed. But static child routes (read-all, cleanup) would be lost for any new route not in the list. Since operation is trusted-through in the sanitizer (not re-validated), a new resource collection outside RESOURCE_SEGMENTS would leak its raw ID. Low risk given the current API surface, but worth a note when the next route lands.

3. port_unconfirmed reported as daemon_failure with daemon_state: "ready"
The port-discovery timeout stamps code: "port_unconfirmed" on a state: "ready" status. daemon-telemetry.ts reports any status with a code, so this fires as ao.renderer.daemon_failure with daemon_state: "ready". Semantically slightly odd ("failure" + "ready"), but the signal is useful (port discovery isn't working) and daemon_state disambiguates. Acceptable design choice — just flagging it so it's not surprising in PostHog dashboards.

4. main.tsx discards the startDaemonFailureTelemetry() stop function
Fine for production (telemetry lives for app lifetime), just noting the return value is test-only.


Coverage check

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

@illegalcall illegalcall merged commit 0f2295c into AgentWrapper:main Jul 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telemetry blind spots: no failure or CTA instrumentation in the desktop renderer

4 participants