From ff7a26a07048e3d081702fad69a7c88ffca26bc3 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 3 Sep 2026 11:48:44 +0200 Subject: [PATCH 1/4] docs: Revamp AGENTS.md with boundaries, skills index, and stack framing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address getsentry/sentry-react-native#6638. Add the three high-value sections the "great AGENTS.md" checklist calls for and that were missing: a three-tier βœ…/⚠️/🚫 Boundaries section, a Skills-load-on-demand index, and a hybrid-SDK Stack framing that points at live version sources rather than copying rot-prone version strings. Fix a drifted diagnostics example (logger -> debug) and add a skill-precedence pointer in packages/core. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 51 ++++++++++++++++++++++++++++++++++++++--- packages/core/AGENTS.md | 12 +++++++--- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4bf2fad3df..9ec88a6602 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,11 +1,21 @@ # AGENTS.md -Sentry React Native SDK β€” monorepo using yarn workspaces with a single package at `packages/core`. +Sentry React Native SDK β€” a **hybrid SDK**: a TypeScript/JS layer on top that wraps the native **sentry-cocoa** (iOS) and **sentry-android/java** SDKs through a JS↔native bridge. Monorepo using yarn workspaces; the SDK itself is the `packages/core` package. + +## Stack + +- **Published packages:** `@sentry/react-native` (`packages/core`, the SDK) + companion `@sentry/expo-upload-sourcemaps`; yarn 4 workspaces. +- **Wraps native SDKs:** sentry-cocoa (iOS) + sentry-android/java (Android) through the JS↔native bridge. +- **Hosts:** React Native on **both** the New (TurboModule/Fabric) and Old (bridge) Architecture, plus Expo and React. +- **Exact versions** (don't copy them here β€” they rot): JS deps and host peer ranges in [`packages/core/package.json`](packages/core/package.json); the bundled native SDK versions per release in [`SDK-VERSIONS.md`](SDK-VERSIONS.md) (regenerated each release by `craft-pre-release.sh`). + +Changes to the wrapped native SDKs can ripple to them and to downstream hybrid SDKs (Flutter, .NET/MAUI, Unity) β€” see *Cross-Platform Dependencies*. ## Agent Responsibilities -- **Continuous Learning**: Document new patterns in the appropriate nested `AGENTS.md` file. -- **Context Management**: After compaction, re-read `AGENTS.md` files relevant to your current task. +- **Reach for a skill first.** For anything beyond a trivial edit, load the matching skill (see *Skills*) β€” it carries the deep, current guidance this file only indexes. +- **Continuous Learning:** when you discover a durable pattern, record it in the nearest nested `AGENTS.md` (or the relevant skill), not inline in a PR. +- **Context Management:** after compaction, re-read the `AGENTS.md` for the surface you're touching before continuing. ## Setup @@ -32,6 +42,26 @@ yarn build | ObjC/C++ lint | `yarn lint:clang` | | Swift lint | `yarn lint:swift` | +## Boundaries + +**βœ… Always** +- Run `yarn lint`, `yarn test`, and `yarn circularDepCheck` before calling a change done; regenerate the API report (`yarn api-report`) when the public surface moved. +- Make a change work on **both** architectures (New + Old) and **both** platforms (iOS + Android) β€” a fix that only covers one is not done. +- Gate any user data placed in events/breadcrumbs/spans/logs on `options.sendDefaultPii`. +- Catch native errors at the bridge boundary and degrade β€” reject/log, never let them propagate. + +**⚠️ Ask first** +- Adding or changing any dependency, `.vscode` extension, GitHub Action, or native dependency β€” verify provenance first (load `code-guidelines` β†’ *Adding dependencies*). +- Changing the public API (`packages/core/src/js/index.ts` barrel, exported options) or a codegen/bridge spec (`NativeRNSentry.ts`, `*NativeComponent.ts`) β€” these are breaking and need a deprecation path. +- Changing CI workflows, release config, or anything under `scripts/`. + +**🚫 Never** +- Crash the host app β€” a native exception that reaches the app is the highest-severity failure this SDK can cause. +- Break the public API or bridge ABI without a `@deprecated` migration path (an app can ship new JS against an older cached native binary). +- Hand-edit generated files: `packages/core/etc/sentry-react-native.api.md` (regenerate it) or New-Architecture codegen output β€” regenerate, never edit by hand. +- Add `CHANGELOG.md` noise for routine internal/CI/test/chore changes β€” the user-facing sections (`### Features`, `### Fixes`) are for user-visible changes. A genuinely notable internal change goes under the `### Internal` section instead, not among the user-facing entries. +- Commit secrets, tokens, or DSNs. + ## Commit Conventions Follow conventional commit format: `(): ` @@ -99,6 +129,21 @@ Changes may impact downstream SDKs. Coordinate with other teams when modifying n - **Inline comments** for complex logic only - Update `CHANGELOG.md` for user-visible changes +## Skills β€” load on demand + +The deep, task-specific guidance lives in `.agents/skills/` (registered in `agents.toml`) and is loaded when you need it β€” this file is the always-on operating manual, the skills are the specialists. Reach for one **before** doing the matching work: + +| Skill | Load it when | +|-------|--------------| +| `spec` | The *what* isn't pinned down yet β€” a fuzzy issue or idea to scope into acceptance criteria. Hands off to `design-first`. | +| `design-first` | Starting a feature/integration, changing the public barrel, crossing the bridge, or changing a codegen spec β€” shape modules and seams before coding. | +| `code-guidelines` | Implementing, refactoring, designing APIs, writing integrations, handling breaking changes, or adding dependencies. | +| `test-guidelines` | Writing, modifying, or reviewing Jest tests, fixtures, and mocks. | +| `review` | A three-axis (Standards / Spec / Correctness) pass on a branch or PR before opening or merging. | +| `diagnosing-bugs` | A hard bug, flaky test, CI hang, native crash, or perf regression β€” builds a red-capable loop before hypothesizing. | + +Warden's automated PR review and `agents.toml` also pull **remote** specialists (`security-review`, `gha-security-review`, `span-convention-review`, and more) β€” invoke those for depth beyond the local pass. + ## Nested AGENTS.md Files - [`packages/core/AGENTS.md`](packages/core/AGENTS.md) β€” TypeScript/JavaScript code style, testing, patterns diff --git a/packages/core/AGENTS.md b/packages/core/AGENTS.md index 8ab7eef783..f2ff70bd84 100644 --- a/packages/core/AGENTS.md +++ b/packages/core/AGENTS.md @@ -1,5 +1,7 @@ # packages/core β€” TypeScript/JavaScript SDK +> **Depth lives in the skills.** The `code-guidelines` and `test-guidelines` skills (`.agents/skills/`) hold the authoritative, current guidance on code style, API/integration design, the bridge, and tests β€” load them before non-trivial work. This file covers the package's build/test commands, quick-reference conventions, and the **TurboModule subsystem**, which is documented nowhere else. Where a convention here overlaps a skill, the skill wins. + ## Build & Test ```bash @@ -95,14 +97,18 @@ describe('MyComponent', () => { ## Common Patterns +> Diagnostics use **`debug`** from `@sentry/core`, not `logger` β€” `logger` is the Logs API and emits log *events* (and recurses in the bridge hot path). See *TurboModule Instrumentation* below. + ### Error Handling ```typescript +import { debug } from '@sentry/core'; + try { const result = await riskyOperation(); return result; } catch (error) { - logger.error('Operation failed', error); + debug.error('Operation failed', error); // Don't throw - log and return fallback return fallbackValue; } @@ -117,14 +123,14 @@ const { RNSentry } = NativeModules; export async function nativeOperation(param: string): Promise { if (!RNSentry) { - logger.warn('Native module not available'); + debug.warn('Native module not available'); return false; } try { return await RNSentry.nativeOperation(param); } catch (error) { - logger.error('Native operation failed', error); + debug.error('Native operation failed', error); return false; } } From 11a1fe169c00718a4088823dbd3d294d43e0fcde Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 3 Sep 2026 11:58:27 +0200 Subject: [PATCH 2/4] docs: Revamp nested AGENTS.md files (android, ios, samples) Extend the AGENTS.md revamp to the nested surface files: add a skill-precedence pointer and scoped Boundaries to android/ and ios/, fix drifted bridge examples to the real `SentryReactNative` reject code, and correct/expand the sample run commands (New/Old Arch toggle for RN, real run:ios/run:android scripts for Expo). Co-Authored-By: Claude Opus 4.8 --- packages/core/android/AGENTS.md | 22 +++++++++++++++++++--- packages/core/ios/AGENTS.md | 29 ++++++++++++++++------------- samples/expo/AGENTS.md | 8 ++++++-- samples/react-native/AGENTS.md | 11 +++++++++++ 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/packages/core/android/AGENTS.md b/packages/core/android/AGENTS.md index 1d60e75b67..804d89905e 100644 --- a/packages/core/android/AGENTS.md +++ b/packages/core/android/AGENTS.md @@ -1,5 +1,7 @@ # packages/core/android β€” Java & Kotlin +> **Depth lives in the skills.** `code-guidelines` (`.agents/skills/`, esp. `references/native-bridge.md`) is authoritative for bridge and native conventions β€” load it before non-trivial work. This file is the quick reference for the Android surface; where a convention here overlaps a skill, the skill wins. + ## Formatting & Linting | Task | Command | @@ -25,13 +27,16 @@ ## Architecture Variants -Android native code supports both old and new React Native architectures: +Android native code supports both React Native architectures β€” a bridge-method change usually has to land in **both**: + - `src/oldarch/` β€” Legacy bridge implementation - `src/newarch/` β€” TurboModule / Fabric implementation -- `src/main/` β€” Shared code +- `src/main/` β€” Shared code (`io.sentry.react`) ## Native Bridge Pattern (Java) +Catch everything at the boundary and reject β€” never let a native exception reach the app. The reject error code is the shared `"SentryReactNative"`, not a per-method code: + ```java @ReactMethod public void nativeOperation(String param, Promise promise) { @@ -39,11 +44,22 @@ public void nativeOperation(String param, Promise promise) { boolean result = performOperation(param); promise.resolve(result); } catch (Exception e) { - promise.reject("OPERATION_FAILED", "Operation failed: " + e.getMessage(), e); + promise.reject("SentryReactNative", e.getMessage(), e); } } ``` +## Boundaries + +**βœ… Always** +- Land a bridge-method change in **both** `src/oldarch/` and `src/newarch/` β€” one arch is not done. +- Catch exceptions at every `@ReactMethod` and `promise.reject(...)` β€” an uncaught native exception crashes the host app. +- Gate any user data added to events/breadcrumbs/spans on `sendDefaultPii`. + +**🚫 Never** +- Change the codegen bridge ABI without mirroring it in JS + iOS and keeping it backward-compatible with an older cached native binary. +- Bump the bundled `io.sentry:sentry-android` version by hand β€” go through `scripts/update-android.sh`. + ## Working with Local sentry-java 1. Build sentry-java: `cd sentry-java && make dryRelease` diff --git a/packages/core/ios/AGENTS.md b/packages/core/ios/AGENTS.md index 04edda1bad..7ca6b02195 100644 --- a/packages/core/ios/AGENTS.md +++ b/packages/core/ios/AGENTS.md @@ -1,5 +1,7 @@ # packages/core/ios β€” Objective-C & Swift +> **Depth lives in the skills.** `code-guidelines` (`.agents/skills/`, esp. `references/native-bridge.md`) is authoritative for bridge and native conventions β€” load it before non-trivial work. This file is the quick reference for the iOS surface; where a convention here overlaps a skill, the skill wins. + ## Formatting & Linting | Task | Command | @@ -22,20 +24,10 @@ - Use **swiftlint** (enforced by CI) - Follow Swift API design guidelines -## Error Handling Pattern - -```objc -NSError *error = nil; -BOOL success = [self performOperation:&error]; -if (!success) { - [SentryLog logWithMessage:[NSString stringWithFormat:@"Operation failed: %@", error] - andLevel:kSentryLevelError]; - return fallback; -} -``` - ## Native Bridge Pattern (Objective-C) +Catch everything at the boundary and reject β€” never let an exception reach the app. The reject error code is the shared `@"SentryReactNative"`, not a per-method code: + ```objc RCT_EXPORT_METHOD(nativeOperation:(NSString *)param resolver:(RCTPromiseResolveBlock)resolve @@ -45,11 +37,22 @@ RCT_EXPORT_METHOD(nativeOperation:(NSString *)param BOOL result = [self performOperation:param]; resolve(@(result)); } @catch (NSException *exception) { - reject(@"OPERATION_FAILED", exception.reason, nil); + reject(@"SentryReactNative", exception.reason, nil); } } ``` +## Boundaries + +**βœ… Always** +- Resolve or reject every exported method β€” a `Promise` left neither resolved nor rejected hangs the JS caller. +- Catch native exceptions at the bridge β€” an exception that reaches the app crashes it. +- Gate any user data added to events/breadcrumbs/spans on `sendDefaultPii`. + +**🚫 Never** +- Reach for `PrivateSentrySDKOnly` from new code β€” route hybrid-SDK access through `RNSentryInternal` (see below). +- Bump the bundled sentry-cocoa version by hand β€” go through `scripts/update-cocoa.sh`. + ## Working with Local sentry-cocoa 1. Build sentry-cocoa: `cd sentry-cocoa && make init` diff --git a/samples/expo/AGENTS.md b/samples/expo/AGENTS.md index 0ecaca367c..fb2193c7b7 100644 --- a/samples/expo/AGENTS.md +++ b/samples/expo/AGENTS.md @@ -1,9 +1,13 @@ # samples/expo β€” Expo Sample App +The test bed for the SDK under Expo (config plugin, Metro integration, EAS). Use it to verify a change behaves in an Expo-managed app, not just a bare RN one. + ## Running ```bash -yarn start +yarn start # Start the dev server (expo start) +yarn ios # Build & run the iOS dev client (expo run:ios) +yarn android # Build & run the Android dev client (expo run:android) ``` -Follow the Expo CLI prompts to open on iOS simulator, Android emulator, or a physical device. +`yarn start` then follows the Expo CLI prompts to open on an iOS simulator, Android emulator, or a physical device. Because the SDK ships native code, plain Expo Go isn't enough β€” the `run:*` scripts build a dev client that includes it. diff --git a/samples/react-native/AGENTS.md b/samples/react-native/AGENTS.md index ef66b6296a..6186c57ac5 100644 --- a/samples/react-native/AGENTS.md +++ b/samples/react-native/AGENTS.md @@ -1,5 +1,7 @@ # samples/react-native β€” React Native Sample App +The end-to-end test bed for the SDK on a bare RN app. Use it to reproduce and verify a change on real devices/simulators, on **both** architectures. + ## Running ```bash @@ -8,6 +10,15 @@ yarn ios # Run iOS app (separate terminal) yarn android # Run Android app (separate terminal) ``` +## New vs Old Architecture + +The sample runs on either architecture β€” toggle, then reinstall pods / rebuild: + +- **iOS:** `RCT_NEW_ARCH_ENABLED=1 npx pod-install` (unset for Old Arch). +- **Android:** `newArchEnabled` in `android/gradle.properties`. + +A native change is only verified once it runs on both. + ## Troubleshooting **General build failures:** From a6148f7f29d512fb159cf54f3c1c9da2f2d066fd Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 3 Sep 2026 12:00:58 +0200 Subject: [PATCH 3/4] docs(ios): Document sample pod-install build matrix Replace the bare pod-install note with the sample's actual `yarn pod-install--[-legacy]` script matrix, mapping each axis to the env it sets (ENABLE_PROD, USE_FRAMEWORKS, ENABLE_NEW_ARCH). Co-Authored-By: Claude Opus 4.8 --- samples/react-native/AGENTS.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/samples/react-native/AGENTS.md b/samples/react-native/AGENTS.md index 6186c57ac5..5220a7880f 100644 --- a/samples/react-native/AGENTS.md +++ b/samples/react-native/AGENTS.md @@ -10,14 +10,23 @@ yarn ios # Run iOS app (separate terminal) yarn android # Run Android app (separate terminal) ``` -## New vs Old Architecture +## iOS: pod install build matrix -The sample runs on either architecture β€” toggle, then reinstall pods / rebuild: +The iOS build varies on three axes, and there's a `yarn` script per combination. Install pods through one of these rather than a bare `pod install` β€” each exports the env the Podfile and `pod update` read (`USE_FRAMEWORKS`, `ENABLE_PROD`, `ENABLE_NEW_ARCH`): -- **iOS:** `RCT_NEW_ARCH_ENABLED=1 npx pod-install` (unset for Old Arch). -- **Android:** `newArchEnabled` in `android/gradle.properties`. +``` +yarn pod-install--[-legacy] +``` + +- **debug / release** β†’ `ENABLE_PROD` (0/1) +- **static / dynamic** β†’ `USE_FRAMEWORKS` framework linkage +- **New Arch (default) / `-legacy`** β†’ `ENABLE_NEW_ARCH` (1/0) + +e.g. `yarn pod-install-debug-static` (New Arch, static frameworks) or `yarn pod-install-release-dynamic-legacy` (Old Arch, dynamic frameworks). A native change is only verified once it runs across the arch and linkage combinations it can affect. + +## Android: architecture toggle -A native change is only verified once it runs on both. +Set `newArchEnabled` (`true`/`false`) in `android/gradle.properties`, then rebuild. ## Troubleshooting From 57f8da29ecb47ada87829d6bce115ee090684154 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 3 Sep 2026 12:09:23 +0200 Subject: [PATCH 4/4] docs(android): Catch Throwable, not Exception, in bridge example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The example caught Exception while the boundary rule says to catch everything and the module code (RNSentryModuleImpl) catches Throwable throughout β€” an Error would otherwise crash the app. Flagged by Copilot. Co-Authored-By: Claude Opus 4.8 --- packages/core/android/AGENTS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/android/AGENTS.md b/packages/core/android/AGENTS.md index 804d89905e..f992a6de72 100644 --- a/packages/core/android/AGENTS.md +++ b/packages/core/android/AGENTS.md @@ -35,7 +35,7 @@ Android native code supports both React Native architectures β€” a bridge-method ## Native Bridge Pattern (Java) -Catch everything at the boundary and reject β€” never let a native exception reach the app. The reject error code is the shared `"SentryReactNative"`, not a per-method code: +Catch `Throwable` at the boundary β€” not just `Exception`, so an `Error` can't crash the app either (the module code catches `Throwable` throughout) β€” and reject with the shared `"SentryReactNative"` code, not a per-method one: ```java @ReactMethod @@ -43,7 +43,7 @@ public void nativeOperation(String param, Promise promise) { try { boolean result = performOperation(param); promise.resolve(result); - } catch (Exception e) { + } catch (Throwable e) { promise.reject("SentryReactNative", e.getMessage(), e); } } @@ -53,7 +53,7 @@ public void nativeOperation(String param, Promise promise) { **βœ… Always** - Land a bridge-method change in **both** `src/oldarch/` and `src/newarch/` β€” one arch is not done. -- Catch exceptions at every `@ReactMethod` and `promise.reject(...)` β€” an uncaught native exception crashes the host app. +- Catch `Throwable` at every `@ReactMethod` and `promise.reject(...)` β€” an uncaught throwable crashes the host app. - Gate any user data added to events/breadcrumbs/spans on `sendDefaultPii`. **🚫 Never**