From 15fca3fa1f1e280eff96f7843959a54730a7e5c7 Mon Sep 17 00:00:00 2001 From: Jake Sciotto Date: Wed, 19 Aug 2026 13:10:18 -0600 Subject: [PATCH 1/3] feat(react-native): record automatic exception steps `addExceptionStep` shipped in 4.50.0, but every step is manual, so an exception carries a timeline only where somebody added a call. Bugsnag and Sentry leave breadcrumbs for navigation, taps and lifecycle on their own. Set `errorTracking.exceptionSteps.automatic` to `true`, or to an object, to record a step for a screen change, an autocaptured tap, or an app lifecycle transition. Every signal stays off by default, because each step adds bytes to every captured exception. The recorder hangs off the existing `processBeforeEnqueue` chokepoint, which already sees `$screen`, `$autocapture` and the lifecycle events, so nothing new is patched or wrapped. Automatic steps share the byte-bounded buffer and the native forwarding that manual steps use, so a native crash carries the same timeline. Automatic steps carry `$type`, which the error tracking timeline already renders. `$type` and `$level` join `EXCEPTION_STEP_INTERNAL_FIELDS` in `@posthog/core` to match that wire contract, and neither becomes reserved, so a caller can still categorise a manual step. A manual step stays untyped. An event that `before_send` dropped leaves no step: a caller drops an event to keep data out of PostHog, so it must not reappear in an exception payload. A tap step carries neither `$el_text` nor touch coordinates, because element text is user-visible copy that can hold personal data. Co-Authored-By: Claude Opus 5 (1M context) --- .../automatic-exception-steps-react-native.md | 8 + .../app/(tabs)/error-tracking.tsx | 4 + examples/example-expo-53/app/posthog.tsx | 5 + .../error-tracking/exception-steps.spec.ts | 41 ++++ .../src/error-tracking/exception-steps.ts | 75 ++++++ .../src/error-tracking/automatic-steps.ts | 102 +++++++++ .../react-native/src/error-tracking/index.ts | 51 +++++ packages/react-native/src/posthog-rn.ts | 10 + .../exception-steps-automatic-capture.spec.ts | 126 ++++++++++ .../test/exception-steps-automatic.spec.ts | 215 ++++++++++++++++++ 10 files changed, 637 insertions(+) create mode 100644 .changeset/automatic-exception-steps-react-native.md create mode 100644 packages/react-native/src/error-tracking/automatic-steps.ts create mode 100644 packages/react-native/test/exception-steps-automatic-capture.spec.ts create mode 100644 packages/react-native/test/exception-steps-automatic.spec.ts diff --git a/.changeset/automatic-exception-steps-react-native.md b/.changeset/automatic-exception-steps-react-native.md new file mode 100644 index 0000000000..27b6071ae9 --- /dev/null +++ b/.changeset/automatic-exception-steps-react-native.md @@ -0,0 +1,8 @@ +--- +'@posthog/core': minor +'posthog-react-native': minor +--- + +Add automatic exception steps to React Native, so a captured exception carries a timeline even where nobody called `addExceptionStep`. Set `errorTracking.exceptionSteps.automatic` to `true`, or to an object, to record a step for a screen change (`navigation`), an autocaptured tap (`taps`), or an app lifecycle transition (`lifecycle`). Every signal stays off by default, because each step adds bytes to every captured exception. + +Automatic steps carry `$type`, which the error tracking timeline already renders, and they share the byte-bounded buffer and the native forwarding that manual steps use. A manual step stays untyped. An event that `before_send` dropped leaves no step. diff --git a/examples/example-expo-53/app/(tabs)/error-tracking.tsx b/examples/example-expo-53/app/(tabs)/error-tracking.tsx index 55496c36ef..ccd2521b41 100644 --- a/examples/example-expo-53/app/(tabs)/error-tracking.tsx +++ b/examples/example-expo-53/app/(tabs)/error-tracking.tsx @@ -112,6 +112,10 @@ export default function ErrorTrackingScreen() { Record breadcrumb-style steps with addExceptionStep. Buffered steps attach to every captured $exception as $exception_steps — including the manual captures above and the native crash below. + + This example also sets errorTracking.exceptionSteps.automatic, so switching tabs, tapping a button + and backgrounding the app each leave their own step. Those steps carry a $type. + {EXCEPTION_STEPS.map(({ label, message, properties }) => (