Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions context/agents/error-tracking/capture-exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,27 @@ never manual capture calls sprinkled across files.
An app that runs code on a server as well as in the browser has two runtimes,
and each needs its own capture. Cover every runtime the docs cover for this
framework and the app actually uses: the server side takes the SDK's server
library, where the framework hands you its errors (for Next.js,
`onRequestError` in `instrumentation.ts`). A browser-only setup leaves every
server error uncaptured. An Electron app has the same split: its main process
library, where the framework hands you its errors (for Next.js 15 and later,
`onRequestError` in `instrumentation.ts`, in both runtimes Next.js calls it
from: Node.js and edge). A browser-only setup leaves every server error
uncaptured. An Electron app has the same split: its main process
runs Node, and its uncaught errors never reach the renderer's capture, so the
main process takes the server library. A packaged app ships without the
developer's `.env`, so a runtime `process.env` lookup in the main process is
always empty there: read the token the way the bundler inlines it at build time
(electron-vite: `import.meta.env.MAIN_VITE_*`). A fully static app has no
server runtime to cover.

A framework hook exists only from the version that introduced it, so read the
installed framework version from the lockfile before you wire one. Next.js 14
never calls `onRequestError`, and runs `instrumentation.ts` only with
`experimental.instrumentationHook: true`. When the installed version has no
hook for its server errors, do not export one it will never call: name the
errors that stay uncaptured in your handoff, and the upgrade that adds the
hook. Create the server client when the server starts (in Next.js, from
`register()`), not on the first failed request, so its process-level
autocapture listens from boot.

A page often shows two setups side by side: a global initializer, and an
explicit client you hold. Follow one of them the whole way through. Taking the
wiring from one and the calls from the other reads fine and compiles nowhere β€”
Expand Down Expand Up @@ -79,6 +90,20 @@ environment, so a build that cannot find an SDK (`SDK location not found`)
failed because of this shell, not your edit: do not retry it, and say so in
your handoff.

## Name the user on server errors

A server error links to the person and the session replay only when its
capture carries the browser's identity. Where the browser calls the app's own
server (one app such as Next.js, or a separate API), send it: set
`tracing_headers` in the browser init to the hostnames the browser calls
(`window.location.hostname` for the app's own requests). On the server, take
the identity from the `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID`
request headers first, then from the PostHog cookie: its distinct ID, and the
session ID from `$sesid` while that session is fresh. Pass the distinct ID to
the capture call and the session ID as `$session_id`, following the docs'
pattern. These values are analytics context: never use them to authenticate
anyone.

## Do not copy the SDK's own listeners

Exception autocapture already registers the global handlers: `window.onerror`
Expand All @@ -96,7 +121,11 @@ global handler fires. Express error middleware, Fastify `setErrorHandler`, Hono
`onError`, Vue `app.config.errorHandler`, Angular `ErrorHandler`, SvelteKit
`handleError`, a React error boundary, Next.js `global-error` β€” hook those,
because the framework swallows the error and the global listener never hears
of it. NestJS is one more layer up: its exception filters answer every request
of it. The boundaries the app already has are such hooks too: add capture to
every one that exists β€” each Next.js `error.tsx`, a custom `global-error.tsx`,
a Pages Router `_error` or `_app` boundary β€” since each swallows what it
catches. Creating new boundaries per route is the hand-wrapping to avoid;
capturing in the ones the app has is not. NestJS is one more layer up: its exception filters answer every request
error before Express error middleware runs, so capture goes in a global filter
(the app's existing `@Catch()` filter when it has one), not in
`setupExpressErrorHandler`. A platform with no autocapture at all, such as Go,
Expand All @@ -111,6 +140,9 @@ and every PostHog library in the manifest is imported. No global error listener
of your own sits beside the SDK's autocapture. The project builds as well as it
did before your edits. You installed nothing but the server library your
capture imports, and did not run tests, search outside the project, or read
through the whole app or hand-wrap individual components or routes. Your handoff names the files you changed, the capture mechanism, and
the build command you ran with its result, so the report can explain it to the
user.
through the whole app or hand-wrap individual components or routes. Every
error boundary the app already had now captures, and server captures carry the
browser's distinct ID and session ID. Your handoff names the files you changed,
the capture mechanism, the server errors that stay uncaptured on the installed
version (none, when every hook exists), and the build command you ran with its
result, so the report can explain it to the user.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ reach PostHog β€” one handler, not hand-wrapped across files.
Follow the framework's own mechanism for a global error handler, using the
reference example and the docs for the exact pattern. Find the init or app entry,
add the handler there. Do not read through the whole app or wrap individual
components or routes by hand.
components or routes by hand. Error boundaries the app already has (a Next.js
`error.tsx`, a custom `global-error.tsx`) swallow what they catch: add capture
to each of them.

If the app already has natural exception boundaries β€” a server-side API error
handler, a critical flow with its own try/catch β€” capturing there too is worth a
Expand Down
Loading