Skip to content

fix(analytics): stop dropping client events captured before PostHog init - #6988

Merged
icecrasher321 merged 4 commits into
stagingfrom
fix/posthog-client-exception-capture
Aug 22, 2026
Merged

fix(analytics): stop dropping client events captured before PostHog init#6988
icecrasher321 merged 4 commits into
stagingfrom
fix/posthog-client-exception-capture

Conversation

@icecrasher321

@icecrasher321 icecrasher321 commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two fixes to client-side exception logging, found while working out why captured crashes weren't diagnosable.

Events captured before init were silently discarded. posthog.capture is a no-op until posthog.init() has run — its entire body sits behind if (this.__loaded) with no buffer and no warning. PostHogProvider reaches init through two dynamic imports (posthog-js and posthog-js/react) while a caller needs only one, so anything captured on mount resolved first and landed in that dead window: login_page_viewed, signup_page_viewed, landing_page_viewed, and — worst — a crash report from an error boundary that fired during first paint. The old typeof posthog.capture === 'function' guard could not catch this; the method exists from module load. Added a readiness gate that PostHogProvider settles on all three branches (enabled, disabled, load-failed), so pre-init events are held and flushed instead of dropped.

Canvas crashes never reached Error Tracking. The workflow canvas error boundary reported them as a custom event with the message copied into a string property — no stack, no grouping, no issue, no session-replay link. It now also calls captureException, which parses the stack into $exception_list. The named event stays: it answers a different question ("how often does the canvas fall over") against a stable name that survives the error tracker's own grouping and resolution.

Browser noise was drowning the real crashes. ResizeObserver loop completed with undelivered notifications was 92% of everything autocapture reported. It is specified behaviour rather than a fault — the observer defers the remaining notifications to the next frame — and it carries no stack that points anywhere useful. Alongside opaque cross-origin Script error. reports and cancellation signals (fetch AbortError, Monaco's CancellationError), it left a handful of genuine crashes unfindable. Filtered in before_send so these never leave the browser. The filter fails open on anything it does not recognize: it runs on every event, so a wrong guess would delete product analytics rather than merely over-report, and a chained exception is dropped only when every link is noise.

Also dropped the now-unnecessary runtime import('posthog-js') from the capture helpers — they take the instance from the provider, so that module is type-only at runtime.

Type of Change

  • Bug fix

Testing

Added apps/sim/lib/posthog/client.test.ts (pre-init hold-and-flush, post-init path, captureException route, analytics-disabled path) and apps/sim/lib/posthog/exception-filter.test.ts (each filtered class, non-exception passthrough, chained exceptions, and the fail-open cases). bun run lint, bun run check:audits (32 audits), check-block-registry, and type-check all pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

posthog.capture is a no-op until init() has run — its body sits behind
if (this.__loaded) with no buffer and no warning. PostHogProvider reaches
init through two dynamic imports while a caller needs only one, so
mount-time events and error-boundary crash reports during first paint
landed in that dead window and were discarded silently.

Also routes the workflow canvas error boundary through captureException so
its crashes reach error tracking with a parsed stack.
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 22, 2026 10:48pm

Request Review

@cursor

cursor Bot commented Aug 22, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches analytics init and a before_send filter on every client event, so a bug could drop product events or exception reports. Filter is fail-open and covered by tests.

Overview
Stops silently dropping client PostHog events that fire before init (mount views and first-paint error-boundary reports). captureClientEvent now waits on a readiness gate that PostHogProvider settles on every path (enabled, disabled, load failure).

The workflow canvas error boundary also calls captureClientException so crashes land in Error Tracking with stack grouping, while keeping the named workflow_canvas_crashed event.

Adds a before_send filter that drops browser-raised ResizeObserver loops, opaque Script error., and cancellation (AbortError / Monaco Canceled) without touching product events or deliberate reports.

Reviewed by Cursor Bugbot for commit 0ba6c70. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR prevents client analytics captured before PostHog initialization from being discarded and improves browser exception reporting.

  • Adds a one-shot readiness gate that flushes queued captures after initialization or safely drops them when analytics is unavailable.
  • Reports workflow-canvas failures through PostHog Error Tracking while retaining the named analytics event.
  • Filters known unactionable browser exceptions and cancellation signals.
  • Revises readiness tests to exercise the singleton lifecycle without test-order or dynamic-import dependencies.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/lib/posthog/client.ts Introduces a one-shot readiness promise used by event and exception capture helpers.
apps/sim/app/_shell/providers/posthog-provider.tsx Settles analytics readiness on enabled, disabled, and load-failure paths and installs exception filtering.
apps/sim/lib/posthog/exception-filter.ts Adds fail-open filtering for known browser-generated exception noise.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx Sends canvas crashes to Error Tracking while preserving the existing named event.
apps/sim/lib/posthog/client.test.ts Tests the complete readiness lifecycle in one self-contained case, resolving the prior ordering issue.
apps/sim/lib/posthog/client-disabled.test.ts Tests the disabled path in an isolated file using static imports, resolving the prior convention issue.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant Gate as PostHog readiness gate
  participant Provider as PostHogProvider
  participant PH as PostHog
  Caller->>Gate: capture event or exception
  alt Analytics initializes
    Provider->>PH: init()
    Provider->>Gate: settle(PH)
    Gate->>PH: flush queued captures
  else Disabled or load fails
    Provider->>Gate: settle(null)
    Gate-->>Caller: discard queued captures safely
  end
  Caller->>Gate: subsequent capture
  Gate->>PH: send directly
Loading

Reviews (2): Last reviewed commit: "test(analytics): make posthog client tes..." | Re-trigger Greptile

Comment thread apps/sim/lib/posthog/client.test.ts Outdated
Comment thread apps/sim/lib/posthog/client.test.ts Outdated
ResizeObserver loop notices were 92% of everything client-side exception
autocapture reported. They are specified behaviour, not faults — the
observer defers the remaining notifications to the next frame — and they
carry no stack that points anywhere. Alongside opaque cross-origin
"Script error." reports and cancellation signals (fetch AbortError,
Monaco's CancellationError), they bury the handful of real crashes.

Filters them in before_send so they never leave the browser. Fails open
on anything unrecognized: a wrong guess here would delete product
analytics, not just over-report.
Two holes in the exception filter, both found reading PostHog's coercers
rather than trusting the shape.

The DOMException coercer always reports type "DOMException" and folds the
name into the value as "AbortError: signal is aborted without reason", so
matching AbortError on type alone never fired for a real aborted fetch —
the filter was dead code for exactly the events it was written for. Match
the error name wherever the coercer put it.

The filter also applied to deliberate captureException reports, which our
error boundaries make. Only exceptions the browser raised itself
(mechanism.handled === false) are now eligible; a report we chose to send
is never dropped.
Readiness is module-level and settles once, so the capture and exception
cases silently depended on an earlier case having settled the gate and
failed when run in isolation. Walk the lifecycle in one case instead.

Moves the disabled-analytics case to its own file, where vitest's per-file
isolation supplies an unsettled gate — dropping the vi.resetModules() and
dynamic import the repo's testing conventions rule out. Replaces the
hand-rolled setTimeout flush with vi.waitFor.
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0ba6c70. Configure here.

@icecrasher321
icecrasher321 merged commit 2c61b29 into staging Aug 22, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/posthog-client-exception-capture branch August 24, 2026 03:00
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.

1 participant