Skip to content

fix(livestream): stop capturing AbortErrors from cancelled SSE streams - #76426

Draft
posthog[bot] wants to merge 1 commit into
masterfrom
posthog-self-driving/fixapp-add-copy-affordances-where-users-a72361
Draft

fix(livestream): stop capturing AbortErrors from cancelled SSE streams#76426
posthog[bot] wants to merge 1 commit into
masterfrom
posthog-self-driving/fixapp-add-copy-affordances-where-users-a72361

Conversation

@posthog

@posthog posthog Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Problem

  • Users can't click-to-copy things they obviously want to copy: the suggested proxy subdomain, values inside CodeSnippet blocks, event names in the Activity table, and Max's answer text are all plain, non-interactive text. Two independent sessions showed people rage-clicking or dead-clicking these exact spots, often while mid-setup (configuring an SDK, bouncing between proxy settings and the Activity page to verify events landed) — the worst time to hit friction.
  • The same 30-minute proxy-setup session also threw 49 DOMException: AbortError: signal is aborted without reason into error tracking (28 on /settings/organization-proxy, 21 on /activity/explore).
  • Pulling the actual error tracking issue behind that message (478 occurrences / 122 sessions / 69 users over 30 days, not just the one session) traced it to api.stream() in lib/api.ts — the helper behind the sidebar's live-notifications SSE connection (and other streaming endpoints). It mounts app-wide, which is why the error shows up on effectively every page, not just the two the report called out. Whenever that connection is torn down early (tab hidden, explicit stop, unmount), the underlying fetch/stream reader can reject with a native AbortError, and stream() had no catch around fetchEventSource() to treat that as the clean cancellation it is — so it reached exception capture as an unhandled rejection instead.

Changes

  • lib/api.ts: wrap api.stream()'s fetchEventSource() call in a try/catch that swallows AbortError (matching the isAbortError convention already used elsewhere in this file) and rethrows anything else.
  • ManagedReverseProxy.tsx: wrap the suggested subdomain example (t.mydomain.com) in the existing CopyToClipboardInline component so it's click-to-copy, since it sits right next to a form asking the user to type that exact value.

I scoped this PR to the two clearest, most defensible fixes rather than the full list of copy affordances the report raised (CodeSnippet per-value copy, Activity table event-name click, a hover copy control on Max message bubbles) — those are real UX friction with existing workarounds (drag-select, whole-block copy) and each is its own small design decision better done as a follow-up than bundled here.

How did you test this code?

  • Added two Jest cases to lib/api.test.ts covering api.stream(): resolves cleanly when the underlying stream rejects with AbortError, and still propagates non-abort errors. Neither existing test exercised this path.
  • pnpm --filter=@posthog/frontend typescript:check and fix (lint + format) — clean on the changed files (pre-existing unrelated failures elsewhere in the repo, from a missing @posthog/quill-primitives package, are untouched by this change).
  • Did not spin up the app to click the new copy affordance manually; relied on CopyToClipboardInline being an existing, already-used component for exactly this pattern (wrapping literal text children for click-to-copy).

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

N/A — no user-facing workflow or API changed.

🤖 Agent context

Autonomy: Fully autonomous

Investigated via the PostHog MCP error-tracking tools to find the actual issue behind the reported AbortError message (not just the two sessions cited in the report), which is what pointed at api.stream() in lib/api.ts rather than the dataNodeLogic.ts/proxy-logic code paths the report's own analysis suggested. Ruled out the dataNodeLogic.ts lazyLoaders count-query catches as a red herring — those don't currently pass an AbortSignal so can't produce this specific native abort message. Descoped the CodeSnippet/Activity-table/Max-bubble copy affordances mentioned in the report to a follow-up, since verifying those needed either a live app session or a larger design call I didn't want to make unreviewed.


Created with PostHog Desktop from this inbox report.

`api.stream()` (used for the sidebar's live notifications SSE connection, live events, and other streaming endpoints) had no guard around `fetchEventSource()` itself. Whenever the connection was torn down early — a visibility-pause disposable, an explicit stop, an unmount — the underlying fetch/stream reader can reject with a native `AbortError`, and with no catch in `stream()` that propagated as an unhandled rejection straight to exception capture instead of being treated as the clean cancellation it is.

Also made the reverse proxy's suggested subdomain example (`t.mydomain.com`) click-to-copy, using the existing `CopyToClipboardInline` component, since it's plain unstyled text right next to a form asking the user to type that exact value in.

branch: posthog-self-driving/fixapp-add-copy-affordances-where-users-a72361

Generated-By: PostHog Code
Task-Id: 7989ab51-32b5-4b6d-953a-db925eac26fe
@trunk-io

trunk-io Bot commented Aug 2, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Bundle size — 🔺 +57 B (+0.0%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 65.51 MiB · 🔺 +57 B (+0.0%)

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.25 MiB · 22 files no change ███░░░░░░░ 27.7% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.13 MiB · 3,035 files 🔺 +56 B (+0.0%) ████████░░ 83.8% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
285.6 KiB ../node_modules/.pnpm/posthog-js@1.409.5/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
235.5 KiB src/taxonomy/core-filter-definitions-by-group.json
231.5 KiB ../node_modules/.pnpm/posthog-js@1.409.5/node_modules/posthog-js/dist/module.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.3 KiB src/lib/api.ts
94.7 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Toolbar bundle — eager 2.19 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.19 MiB · 17 files no change ████░░░░░░ 38.3% of 5.72 MiB
Deferred (lazy) 2.08 MiB · 33 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
718.3 KiB dist/toolbar/toolbar-app-FT4UJ3IP.css
551.4 KiB dist/toolbar/chunk-chunk-AGSGCCBT.js
484.6 KiB dist/toolbar/chunk-chunk-T44C2V5C.js
133.6 KiB dist/toolbar/chunk-chunk-KBLX73CM.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.0 KiB dist/toolbar/toolbar-app-FKUW2FOM.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-3JVI3ZTF.js
20.9 KiB dist/toolbar/chunk-chunk-HJ3ZJMTU.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

Dist folder size — 🔺 +3.9 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1381.31 MiB · 🔺 +3.9 KiB (+0.0%)

@posthog posthog Bot added the run-ci-frontend Force ci-storybook's full visual-regression matrix to run even on a draft PR label Aug 2, 2026
@posthog posthog Bot closed this Aug 2, 2026
@posthog posthog Bot reopened this Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-ci-frontend Force ci-storybook's full visual-regression matrix to run even on a draft PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants