Skip to content

[DO NOT MERGE] feat(core): add opt-in assertion instrumentation - #6592

Draft
antonis wants to merge 11 commits into
mainfrom
feat/loud-invariants
Draft

[DO NOT MERGE] feat(core): add opt-in assertion instrumentation#6592
antonis wants to merge 11 commits into
mainfrom
feat/loud-invariants

Conversation

@antonis

@antonis antonis commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

⚠️ this is a hackweek experiment for now

📜 Description

Adds an opt-in Metro/Babel transform that rewrites assertion call sites (invariant, assert, warning, console.assert) so a violated assertion reports a non-fatal Sentry event instead of being stripped from release bundles or crashing with a minified message.

  • Enabled via captureAssertions in the Sentry Metro config; off by default. First-party code is instrumented by default; dependencies are opt-in through an includeNodeModules allowlist.
  • Hard preconditions (invariant/assert) still throw after reporting; warning/console.assert are report-only.
  • New public runtime API captureAssertionViolation() (also usable by hand).
  • Every violation reports a uniform mechanism.type = 'assertion'; the specific pragma is recorded under mechanism.data.pragma. Grouping keys by pragma + call site, so flavors stay separable without fragmenting the mechanism type.
  • Fixes a related gap in the RN global error handler so an error already captured by Sentry and re-thrown isn't reported a second time.

💡 Motivation and Context

Assertions are usually stripped from release builds or, when they do fire, surface as unreadable minified crashes. This surfaces them as grouped, non-fatal Sentry events with the condition, runtime values, and a stack anchored at the assertion site — without changing app source.

The public surface uses an "assertions" vocabulary (captureAssertions, captureAssertionViolation, AssertionViolationOptions) rather than "invariant" so the naming isn't tied to a single pragma.

💚 How did you test it?

Unit tests for the reporter, the Babel plugin, the transformer wiring, and the RN error handler — including a plugin↔runtime coupling test that fails CI if the injected helper name and the exported reporter name drift apart. Manually verified on the RN sample (iOS): invariant from a dependency, first-party console.assert, and a hand-written call each produced the expected handled events.

📝 Checklist

  • I added tests to verify changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.
  • No breaking changes.

🔮 Next steps

If this graduates from a hackweek experiment to a real feature, revisit before release:

  • Gate values PII (primitives always; object/array snapshots behind sendDefaultPII).
  • Make reporting release-only / configurable (currently reports in dev too).
  • User docs: opt-in setup, lazy message/args evaluation, statement-position-only coverage.
  • Mobile SDK team sign-off on the new public API and the global error-handler guard.

Add an opt-in Metro/Babel transform that rewrites assertion call sites
(invariant, assert, warning, console.assert) so a violated assertion
reports a non-fatal Sentry event instead of being stripped from release
bundles or crashing with a minified message.

Adds the captureInvariantViolation runtime API and fixes a related gap
in the RN global error handler so an already-captured, re-thrown error is
not reported a second time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • [DO NOT MERGE] feat(core): add opt-in assertion instrumentation by antonis in #6592
  • test(e2e): De-flake TurboModule span-attribute count assertion by antonis in #6596
  • chore(e2e): Bump Android binary size diff limit to 5.8 MiB by antonis in #6595
  • docs: update CONTRIBUTING paths for the monorepo layout by melbinjp in #6594

🤖 This preview updates automatically when you update the PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@antonis antonis added the ready-to-merge Triggers the full CI test suite label Aug 18, 2026
@sentry

sentry Bot commented Aug 18, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
Sentry RN io.sentry.reactnative.sample 8.23.0 (103) Release

⚙️ sentry-react-native Build Distribution Settings

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3853.25 ms 1217.31 ms -2635.94 ms
Size 5.08 MiB 6.72 MiB 1.64 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
ecf47a2+dirty 3835.20 ms 1222.43 ms -2612.77 ms
3817909+dirty 1183.90 ms 1187.50 ms 3.60 ms
5ee78d6+dirty 3847.52 ms 1224.27 ms -2623.25 ms
890d145+dirty 1223.59 ms 1231.37 ms 7.78 ms
e763471+dirty 3841.16 ms 1218.06 ms -2623.09 ms
3d31fcf+dirty 3838.09 ms 1223.46 ms -2614.63 ms
9210ae6+dirty 3815.93 ms 1214.14 ms -2601.79 ms
26843eb+dirty 3861.60 ms 1238.77 ms -2622.82 ms
7a89652+dirty 3861.46 ms 1229.61 ms -2631.85 ms
9ad4522+dirty 3850.40 ms 1222.40 ms -2628.00 ms

App size

Revision Plain With Sentry Diff
ecf47a2+dirty 4.98 MiB 6.46 MiB 1.49 MiB
3817909+dirty 3.38 MiB 4.73 MiB 1.35 MiB
5ee78d6+dirty 5.15 MiB 6.69 MiB 1.53 MiB
890d145+dirty 3.38 MiB 4.77 MiB 1.38 MiB
e763471+dirty 4.98 MiB 6.51 MiB 1.53 MiB
3d31fcf+dirty 4.98 MiB 6.56 MiB 1.58 MiB
9210ae6+dirty 5.15 MiB 6.68 MiB 1.53 MiB
26843eb+dirty 4.98 MiB 6.53 MiB 1.55 MiB
7a89652+dirty 5.15 MiB 6.70 MiB 1.55 MiB
9ad4522+dirty 4.98 MiB 6.56 MiB 1.58 MiB

Previous results on branch: feat/loud-invariants

Startup times

Revision Plain With Sentry Diff
5862bee+dirty 3843.09 ms 1212.61 ms -2630.47 ms

App size

Revision Plain With Sentry Diff
5862bee+dirty 5.08 MiB 6.72 MiB 1.64 MiB

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3835.93 ms 1211.10 ms -2624.83 ms
Size 5.08 MiB 6.72 MiB 1.64 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
ad66da3+dirty 3855.02 ms 1213.43 ms -2641.59 ms
7d8c8bd+dirty 3847.98 ms 1230.77 ms -2617.21 ms
4acd4e9+dirty 3806.73 ms 1208.48 ms -2598.25 ms
c2e182c+dirty 3841.76 ms 1220.76 ms -2621.01 ms
3d377b5+dirty 1201.55 ms 1201.80 ms 0.25 ms
c004dae+dirty 3857.82 ms 1224.87 ms -2632.95 ms
a858ac3+dirty 3850.72 ms 1226.35 ms -2624.37 ms
4e0b819+dirty 3828.96 ms 1205.64 ms -2623.32 ms
5c1e987+dirty 1208.43 ms 1220.72 ms 12.29 ms
882f8ae+dirty 3842.51 ms 1230.40 ms -2612.11 ms

App size

Revision Plain With Sentry Diff
ad66da3+dirty 5.15 MiB 6.67 MiB 1.51 MiB
7d8c8bd+dirty 5.15 MiB 6.68 MiB 1.53 MiB
4acd4e9+dirty 4.98 MiB 6.53 MiB 1.55 MiB
c2e182c+dirty 4.98 MiB 6.51 MiB 1.53 MiB
3d377b5+dirty 3.38 MiB 4.76 MiB 1.38 MiB
c004dae+dirty 5.15 MiB 6.67 MiB 1.51 MiB
a858ac3+dirty 4.98 MiB 6.51 MiB 1.53 MiB
4e0b819+dirty 4.98 MiB 6.46 MiB 1.49 MiB
5c1e987+dirty 3.38 MiB 4.73 MiB 1.35 MiB
882f8ae+dirty 5.15 MiB 6.70 MiB 1.54 MiB

Previous results on branch: feat/loud-invariants

Startup times

Revision Plain With Sentry Diff
5862bee+dirty 3846.92 ms 1218.71 ms -2628.21 ms

App size

Revision Plain With Sentry Diff
5862bee+dirty 5.08 MiB 6.72 MiB 1.64 MiB

@antonis antonis removed the ready-to-merge Triggers the full CI test suite label Aug 18, 2026
Rename the public API from invariant/loudInvariants to an assertions
vocabulary: `loudInvariants` config → `captureAssertions`,
`captureInvariantViolation` → `captureAssertionViolation`,
`InvariantViolationOptions` → `AssertionViolationOptions`,
`DEFAULT_INVARIANT_MECHANISM` → `DEFAULT_ASSERTION_MECHANISM`.

Report a uniform `mechanism.type = 'assertion'` for every violation and
move the specific pragma to `mechanism.data.pragma`. Add a plugin↔runtime
coupling test asserting the injected `CAPTURE_FN` matches the exported
reporter name so a one-sided rename fails CI instead of breaking silently.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@antonis antonis changed the title feat(core): add opt-in loud invariants assertion instrumentation feat(core): add opt-in assertion instrumentation Aug 18, 2026
@antonis antonis added the ready-to-merge Triggers the full CI test suite label Aug 18, 2026
Add a CHANGELOG caveat that hard preconditions (invariant/assert) still
throw after reporting — the transform yields a readable event, not crash
suppression. Update the RN global handler comment to the assertions
vocabulary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/core/src/js/assertion.ts
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts Outdated
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
Comment thread packages/core/etc/sentry-react-native.api.md
Address review findings on the assertion instrumentation:

- assertion.ts: stringify values defensively so a Symbol (String(symbol)
  throws) or a value with a throwing toString/Symbol.toPrimitive can't crash
  the reporting path; fall back to [unstringifiable <type>].
- sentryAssertionBabelPlugin.ts: capture only identifiers whose binding is
  visible from the call-site scope, so a nested arrow/callback param no longer
  emits an out-of-scope reference (ReferenceError on the report path).
- sentryAssertionBabelPlugin.ts: normalize path separators before matching so
  the SDK self-exclusion and the includeNodeModules allowlist work on Windows.
- Document that aliased/destructured pragmas (e.g. const { assert } = console)
  are intentionally not matched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/core/src/js/assertion.ts Outdated
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
Comment thread packages/core/src/js/tools/sentryBabelTransformerUtils.ts
Comment thread packages/core/src/js/assertion.ts Outdated
Comment thread packages/core/test/assertion.test.ts
- Guard the reporter's `values` handling against `null` and throwing getters
  so the no-throw reporting path holds for hand-written calls.
- Support `util.format`-style message interpolation via a new `messageArgs`
  option; the Babel plugin forwards variadic pragma args (e.g. RN's
  `invariant(dims, 'No dimension set for key %s', key)`).
- Construct the call-site `Error` through a hoisted global-`Error` alias so a
  lexical `Error` shadow can't break the injected `new Error()`.
- Normalize path separators in the transformer's node_modules gate so the
  `includeNodeModules` allowlist matches on Windows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/core/src/js/assertion.ts Outdated
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
- Guard the `%d`/`%i`/`%f` coercions in the reporter's message formatter so a
  non-coercible arg (e.g. a Symbol) renders as `NaN` instead of throwing on the
  no-throw reporting path.
- Drop `let`/`const` identifiers declared textually after the call site from the
  captured `values`, so a short-circuited condition can't read them in their
  temporal dead zone and throw a ReferenceError on the report path.
- Only rewrite standalone assertion statements; a pragma used as a subexpression
  (e.g. `warning(cond) && next()`) is left untouched to preserve its semantics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
…o__ keys, and @sentry-internal

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
Comment thread packages/core/src/js/assertion.ts Outdated
Comment on lines +225 to +237
for (const key of Object.keys(values)) {
try {
const value = values[key];
data[`values.${key}`] = typeof value === 'boolean' ? value : truncate(stringifyValue(value), MAX_VALUE_LENGTH);
} catch (_e) {
// A throwing getter must not break the no-throw reporting path.
data[`values.${key}`] = '[unreadable]';
}
}
try {
data.values = truncate(JSON.stringify(values) ?? 'undefined', MAX_SNAPSHOT_LENGTH);
} catch (_e) {
// Circular or non-serializable values — the flattened entries above still apply.

@sentry-warden sentry-warden Bot Aug 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unbounded values iteration in flattenValues allows DoS via large objects

flattenValues iterates over every key in values without a count cap and calls JSON.stringify(values) on the full object before truncation, allowing a large input object to exhaust CPU and memory.

Evidence
  • flattenValues calls Object.keys(values) and loops over every key, creating one data entry per key with no limit on the total number of entries.
  • It then calls JSON.stringify(values) on the entire object before passing the result to truncate, so a large object is fully serialized first.
  • Individual values and snapshots are length-capped (MAX_VALUE_LENGTH and MAX_SNAPSHOT_LENGTH), but the key count is unbounded, contradicting the function's JSDoc claim that a large object "can't bloat the event payload".
  • Because captureAssertionViolation is a public API, a caller can pass an arbitrarily large values object (e.g. a config map or array with millions of indices), causing unbounded CPU and memory consumption on the no-throw reporting path.

Identified by Warden · find-bugs, code-review · CTS-DF4

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. flattenValues now caps the entry count at MAX_VALUE_ENTRIES (50) and builds the JSON snapshot from only that capped subset rather than JSON.stringify-ing the full input, so a large object handed to the public API no longer produces unbounded entries or forces a full serialization on the no-throw path. Over-cap keys are summarized in a values.__truncated__ marker, and the JSDoc is updated to describe all three bounds (count, per-entry length, snapshot length). Added a test with a 1000-key object (162eefb4ed8f2d).

…ptions

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 412.00 ms 433.39 ms 21.39 ms
Size 49.74 MiB 55.45 MiB 5.71 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
f9c1ed4+dirty 431.00 ms 466.22 ms 35.22 ms
c2e182c+dirty 471.64 ms 553.59 ms 81.95 ms
41d6254+dirty 424.45 ms 474.34 ms 49.89 ms
af33f3b+dirty 434.90 ms 506.14 ms 71.24 ms
882f8ae+dirty 399.98 ms 427.06 ms 27.08 ms
0b5120f+dirty 503.22 ms 538.60 ms 35.38 ms
774257e+dirty 407.09 ms 447.46 ms 40.37 ms
bf168a4+dirty 418.21 ms 489.74 ms 71.53 ms
5569641+dirty 406.43 ms 428.51 ms 22.08 ms
c151573+dirty 530.34 ms 559.43 ms 29.09 ms

App size

Revision Plain With Sentry Diff
f9c1ed4+dirty 49.74 MiB 54.86 MiB 5.12 MiB
c2e182c+dirty 49.74 MiB 54.85 MiB 5.11 MiB
41d6254+dirty 48.30 MiB 53.60 MiB 5.30 MiB
af33f3b+dirty 49.74 MiB 55.09 MiB 5.35 MiB
882f8ae+dirty 48.30 MiB 53.60 MiB 5.29 MiB
0b5120f+dirty 48.30 MiB 53.58 MiB 5.28 MiB
774257e+dirty 48.30 MiB 53.58 MiB 5.28 MiB
bf168a4+dirty 49.74 MiB 55.09 MiB 5.35 MiB
5569641+dirty 48.30 MiB 53.48 MiB 5.18 MiB
c151573+dirty 48.30 MiB 53.54 MiB 5.24 MiB

Previous results on branch: feat/loud-invariants

Startup times

Revision Plain With Sentry Diff
5862bee+dirty 515.51 ms 569.21 ms 53.70 ms

App size

Revision Plain With Sentry Diff
5862bee+dirty 49.74 MiB 55.45 MiB 5.71 MiB

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 438.85 ms 448.13 ms 9.27 ms
Size 49.74 MiB 55.45 MiB 5.71 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
7436d0f+dirty 429.58 ms 452.52 ms 22.94 ms
1122a96+dirty 510.16 ms 542.00 ms 31.84 ms
26843eb+dirty 442.02 ms 506.29 ms 64.27 ms
88735e9+dirty 427.04 ms 487.37 ms 60.33 ms
882f8ae+dirty 408.19 ms 435.86 ms 27.67 ms
1a2e7e0+dirty 451.98 ms 501.50 ms 49.52 ms
68672fc+dirty 407.55 ms 442.96 ms 35.41 ms
4966363+dirty 415.67 ms 448.60 ms 32.93 ms
038a6d7+dirty 499.02 ms 527.68 ms 28.66 ms
6176a94+dirty 403.58 ms 446.73 ms 43.15 ms

App size

Revision Plain With Sentry Diff
7436d0f+dirty 48.30 MiB 53.60 MiB 5.30 MiB
1122a96+dirty 48.30 MiB 53.54 MiB 5.24 MiB
26843eb+dirty 49.74 MiB 55.26 MiB 5.52 MiB
88735e9+dirty 49.74 MiB 54.82 MiB 5.07 MiB
882f8ae+dirty 48.30 MiB 53.60 MiB 5.29 MiB
1a2e7e0+dirty 49.74 MiB 54.82 MiB 5.07 MiB
68672fc+dirty 48.30 MiB 53.61 MiB 5.31 MiB
4966363+dirty 48.30 MiB 53.54 MiB 5.24 MiB
038a6d7+dirty 48.30 MiB 53.60 MiB 5.30 MiB
6176a94+dirty 48.30 MiB 53.54 MiB 5.24 MiB

Previous results on branch: feat/loud-invariants

Startup times

Revision Plain With Sentry Diff
5862bee+dirty 417.62 ms 440.60 ms 22.98 ms

App size

Revision Plain With Sentry Diff
5862bee+dirty 49.74 MiB 55.45 MiB 5.71 MiB

Comment thread packages/core/src/js/assertion.ts Outdated
Comment thread packages/core/src/js/tools/sentryAssertionBabelPlugin.ts
Comment thread packages/core/test/assertion.test.ts
antonis and others added 2 commits August 19, 2026 17:07
- Guard the error.message backfill so a frozen/read-only/non-object error
  can't throw on the no-throw reporting path.
- Match node_modules as a real path segment (not a raw substring) via a
  shared isInNodeModules() helper, so first-party files like
  node_modules_helper.ts are still instrumented.
- Build siteId from a root-relative path with a parentDir/basename
  fallback, so files sharing a basename no longer collide in the runtime
  dedup set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@antonis

antonis commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

PII note — values are not gated behind sendDefaultPII

Unchecking the "No new PII / gated by sendDefaultPII" checklist box to reflect actual behavior. The assertion transform captures the runtime values of identifiers referenced in the condition and ships them in mechanism.data unconditionally (size-bounded, not sensitivity-bounded). For invariant(user) or assert(session.token) that puts the whole object into the event, with no sendDefaultPII gate.

This is a deliberate call for the experimental/hackweek landing — capturing the failing value is the core point of the feature, and gating it off-by-default would neuter it. If this graduates to an actual feature, revisit before release: likely "primitives always, full object/array snapshots behind sendDefaultPII", plus a Mobile SDK team sign-off on the public API.

@antonis antonis changed the title feat(core): add opt-in assertion instrumentation [DO NOT MERGE] feat(core): add opt-in assertion instrumentation Aug 20, 2026
Comment on lines +169 to +172
if (normalizedFilename.includes('node_modules')) {
if (!inc) {
return undefined;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First-party files with node_modules in path incorrectly skipped from assertion instrumentation

The includes('node_modules') substring check over-matches and skips first-party files like src/utils/node_modules_helper.ts, while the plugin itself correctly instruments them. Use the same path-segment regex the plugin uses.

Evidence
  • sentryAssertionBabelPlugin.ts:396 uses /(?:^|\/)node_modules\// in isInNodeModules, explicitly documented to allow first-party files like src/utils/node_modules_helper.ts.
  • addSentryCaptureAssertionsPlugin uses normalizedFilename.includes('node_modules') which matches any substring, not just a path segment.
  • A first-party file named node_modules_helper.ts is skipped by the transformer guard but would be instrumented by the plugin, so the early return removes valid instrumentation.
  • The plugin's own test (sentryAssertionBabelPlugin.test.ts:252-254) asserts that node_modules_helper.ts should be instrumented.
Also found at 1 additional location
  • packages/core/src/js/tools/sentryAssertionBabelPlugin.ts:231-237

Identified by Warden · code-review · EPW-EC8

Comment on lines +209 to +215
* honors the flag — React Native's ErrorUtils global handler skip it instead of
* reporting the re-thrown error a second time as an unhandled crash. Set on
* *every* rethrow path (including the dedup-suppressed branch, which never
* reaches the tail) so the guard can never be bypassed.
*/
function rethrowCaptured(error: Error): never {
addNonEnumerableProperty(error as unknown as Record<string, unknown>, '__sentry_captured__', true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rethrowCaptured can throw wrong error for frozen errors, bypassing dedup guard

rethrowCaptured lacks a try-catch around addNonEnumerableProperty, so a frozen or sealed error causes a TypeError instead of the original error and skips the __sentry_captured__ tag. If the dedup path also reaches rethrowCaptured for a frozen error, the same conclusion applies.

Evidence
  • rethrowCaptured does addNonEnumerableProperty(error, '__sentry_captured__', true) then throw error with no surrounding try-catch.
  • addNonEnumerableProperty uses Object.defineProperty, which throws TypeError when adding a new property to a frozen object.
  • The comment above rethrowCaptured explicitly states the tag is set on every rethrow path so the guard can never be bypassed, yet it can be.
  • setupErrorUtilsGlobalHandler in reactnativeerrorhandlers.ts (also changed in this PR) checks error?.__sentry_captured__ to avoid reporting the re-thrown error a second time; if the tag was never set, the violation is reported again as an unhandled crash.

Identified by Warden · find-bugs · T8D-QC8

@antonis antonis removed the ready-to-merge Triggers the full CI test suite label Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor
Fails
🚫 Pull request is not ready for merge, please add the "ready-to-merge" label to the pull request

Generated by 🚫 dangerJS against ccca2e2

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