code-guidelines
SkillMonitoring & opsLets your agent follow Sentry React Native SDK coding rules when writing, refactoring, or reviewing code.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the code-guidelines skill
About this capability
Enforce Sentry React Native SDK code guidelines for implementation, refactoring, and review. Use when implementing features, adding functionality, refactoring, reviewing code, designing APIs, changing the public API surface (the `packages/core/src/js/index.ts` barrel), handling breaking changes, dep
What this skill tells your AI
The instructions your AI receives, as published by getsentry/sentry-react-native in .agents/skills/code-guidelines/SKILL.md and read by ahel’s review.
Apply these guidelines to all new and modified code in packages/core. Existing code may not follow these conventions — do not refactor it unless asked.
This SDK spans four surfaces: TypeScript/JS (packages/core/src/js/), Android (Java/Kotlin, packages/core/android/), iOS (ObjC/Swift, packages/core/ios/), and the JS↔native bridge (RNSentry TurboModule / legacy NativeModule) that connects them. Read the AGENTS.md for the surface you're touching — root, packages/core/, packages/core/android/, packages/core/ios/ — before writing. The bridge is where most SDK-specific bugs live; the deep guidance for it is in packages/core/AGENTS.md.
For non-trivial work — a new feature or integration, a public barrel-file change, or a change that crosses the native bridge — load the design-first skill to shape the modules and seams before writing code. When writing or modifying tests, also load the test-guidelines skill.
SDK Development Rules
Integrations
SDK features are packaged as integration factory functions that return an Integration (from @sentry/core). See src/js/integrations/ for the canonical shape (e.g. nativelinkederrors.ts):
const INTEGRATION_NAME = 'MyFeature';
export const myFeatureIntegration = (options: Partial<MyOptions> = {}): Integration => {
return {
name: INTEGRATION_NAME,
setupOnce: () => { /* one-time global side effects, if any */ },
setup: (client: Client) => { /* per-client wiring */ },
processEvent: (event, hint, client) => { /* enrich/drop */ return event; },
};
};
- Check prerequisites and feature flags early — if the feature is disabled or the native module is missing, log via
debugand return without wiring anything up. - Name the integration with a module-level
INTEGRATION_NAMEconstant; don't inline the string. - Integrations should be order-independent. If yours must run before/after another, reconsider the design.
- Register default integrations in
src/js/integrations/default.ts; keep an integration's options and defaults with the integration, not scattered acrossoptions.ts. - Clean up anything global (listeners, timers) — an integration that arms a timer or subscribes to an emitter must have a path that tears it down.
Native Bridge (JS side)
Every call into native goes through the NATIVE wrapper (src/js/wrapper.ts) / RNSentry TurboModule. Rules:
- Guard on availability. If
enableNativeis false or the module isn't linked, degrade gracefully — return a safe fallback, never throw into user code. - Never let a native rejection escape. Wrap bridge calls; log via
debugand return a fallback. A thrown promise rejection from the bridge becomes an unhandled rejection in the host app. - Everything crossing the bridge must be serializable — plain JSON (no functions, class instances,
undefinedholes, circular refs, orBigInt). Both the New Architecture (JSI/TurboModule codegen) and the legacy bridge serialize payloads; a non-serializable value is silently dropped or throws on the native side. VerifytoJSON/normalization round-trips. - Do not wrap
RNSentry's own scope-sync methods (setTag,setContext,addBreadcrumb,setUser,captureEnvelope, …). Wrapping them recurses — see theRNSENTRY_SKIP/ignoreTurboModulesnotes inpackages/core/AGENTS.md. - In this hot path,
loggerfrom@sentry/coreis the Logs API (emits log events), not the debug logger. Usedebugfor diagnostics — see the TurboModule section ofpackages/core/AGENTS.md.
Native Bridge (native side)
- Android (
packages/core/android/): resolve/reject thePromiseon every path; never leak it. Release JNI local refs; don't let a native exception cross back into the bridge uncaught. Old vs new arch code lives insrc/oldarch/andsrc/newarch/; shared code insrc/main/. - iOS (
packages/core/ios/): prefix classesRNSentry; use nullability annotations; watch for retain cycles in blocks (captureweakSelf). Route hybrid-SDK access throughRNSentryInternal(seepackages/core/ios/AGENTS.md), not deprecatedPrivateSentrySDKOnly. - A native exception must never crash the host app — catch at the bridge boundary and reject/log instead.
Codegen (New Architecture)
The bridge spec is codegen'd. src/js/NativeRNSentry.ts is the TurboModule spec; RNSentryReplayMask*NativeComponent.ts are Fabric component specs.
- Codegen only supports a restricted type vocabulary — primitives,
Object(untyped map), arrays, and nullable via?. No unions of literals, no generics, noRecord<K,V>with non-string keys. If a method's shape can't be expressed, pass anObjectand validate/parse on the native side. - Any change to a
Native*.tsspec is a bridge ABI change: it must land in lockstep across the JS spec,ios/RNSentry.mm, and the AndroidRNSentryModuleImpl, and stay backward-compatible with older native binaries a user may have cached. Treat it like a public API change.
Usage tracking & SDK metadata
The event's SDK interface carries sdk.integrations, sdk.packages, and an optional sdk.features list; the spec says a feature should be reported through either an integration or the features list, not both. RN takes the integrations route: unlike sentry-dart (which populates sdk.features via an explicit addFeature / SentryFeatures API), RN's @sentry/core reports usage only through event.sdk.integrations and does not populate sdk.features. So usage is reported implicitly:
- An installed integration is auto-reported by its
name—@sentry/corecollects the names of installed integrations intoevent.sdk.integrations. So the practical rule is: give the integration a stable, correctINTEGRATION_NAME, and register it (a default inintegrations/default.ts, orclient.addIntegration(...)) — that registration is the usage signal. A feature that runs without a registered, named integration is invisible to usage tracking. - SDK identity/packages come from
src/js/version.ts(SDK_NAME,SDK_PACKAGE_NAME,SDK_VERSION) surfaced byintegrations/sdkinfo.ts, which also appends the native SDK package — not the place to register feature usage. - Do not confuse this with
addFeatureFlagon the native module (wrapper.ts/NativeRNSentry.ts) — that is the user-facing feature-flags product, unrelated to SDK usage tracking.
When adding something you want measured, make sure it lands as a named, registered integration rather than a bare side effect.
Logging
debug(from@sentry/core) is the internal diagnostic logger — use it for all SDK diagnostics. It is tree-shaken / gated so it stays quiet in production.logger(from@sentry/core) is the Logs API — it emits user-visible log events. Never use it for internal diagnostics, and never in a hot path (see bridge rules above).- Log at the right level:
debugfor lifecycle/config,warnfor recoverable/degraded paths (native module missing, option ignored),errorfor failures that affect SDK behavior.
Privacy (PII)
- Never collect PII without gating on
options.sendDefaultPii. This covers IP inference, device identifiers, deep-link URLs, navigation params, request/response bodies, and user-supplied context. - Flag any change that could place user data into breadcrumbs, event payloads, span attributes, or logs for review.
- Deliberate exceptions exist and must be documented where they live (e.g. TurboModule module/method names are app-defined identifiers sent regardless of
sendDefaultPii— seepackages/core/AGENTS.md"Privacy"). Don't add new always-on data collection without that justification.
Breaking changes
- The public API is the
src/js/index.tsbarrel plus every exported option inoptions.ts. Its shape is captured in the API report (packages/core/etc/sentry-react-native.api.md); regenerate withyarn api-reportand check withyarn api-report:check. - Removing or changing the signature/behavior of an exported symbol or option is a semver-major breaking change. Prefer deprecation with a migration path (
@deprecatedJSDoc + a working shim) over immediate removal. - Bridge/ABI changes (see Codegen) are breaking even when the JS surface looks unchanged — an app can ship new JS against an older cached native binary.
Adding dependencies
When adding or changing any third-party reference — an npm dependency, a .vscode/extensions.json recommendation, a GitHub Action (uses:), or a native dependency (Podfile / Gemfile / Gradle) — verify it is published by its legitimate owner before referencing it. The tool being real is not enough: the namespace must be one the project trusts. Prefer Sentry's own scope/org (@sentry/*, getsentry/*), then the artifact's documented official publisher, and pin to an exact version / full commit SHA rather than a floating tag. Treat an unscoped or unfamiliar-publisher name as a supply-chain risk until proven otherwise — a claimable namespace lets an attacker ship code to every contributor. Provenance is necessary but not sufficient: a legitimate owner's account or CI can be compromised and ship a malicious version, so pin to an immutable ref (lockfile integrity hash / full SHA) and check advisories for that specific version, not just the publisher. See references/supply-chain.md for the surface-by-surface checklist and verification commands.
File Organization
Group by feature, not by type. A processor or helper a feature owns lives with that feature (the TurboModule files sit together as turbomodule/ + integrations/turboModuleContext*.ts). The integrations/ directory is a type-bucket — it collects things that share the Integration type. Don't treat "it's an integration" as the whole home for a cohesive subsystem; keep the subsystem's own logic together and let the integration file be thin wiring.
Native code lives under packages/core/android/ and packages/core/ios/ and is reached only through the NATIVE wrapper seam — features call the bridge, they don't embed native code.
Where a given piece goes is a locality judgment — see design-first.
TypeScript & API style
Shape the public surface deliberately (see design-first for module shape). In an SDK these matter more than in app code:
- Private by default. Don't export a symbol from the barrel unless it's genuinely part of the SDK's API — every export is a breaking-change liability and shows up in the API report.
- Explicit types on public functions — annotate parameters and return types; don't rely on inference across the API boundary.
unknownoverany. Narrow with type guards;anydisables the checker exactly where SDK robustness matters.- Prefer
interfacefor object shapes; usetypefor unions/mapped types. - Optional chaining / nullish coalescing (
?.,??) over hand-rolled truthiness — but remember??and?.treat0/''/falsecorrectly where||doesn't. - Re-throw, don't wrap-and-rethrow. Preserve the original error and its stack; when adding context, attach a
causerather than replacing the error — stack-trace fidelity is the product. - No cross-boundary deep imports. Import from
@sentry/core/@sentry/browserpublic entry points, not their internal paths; keep RN-internal imports withinsrc/js.
Style enforced by ESLint/oxlint/Prettier (single quotes, trailing commas, 120 cols, arrow-paren avoidance, import ordering) is not restated here — don't flag what yarn lint / yarn fix already handles. See packages/core/AGENTS.md for the list.
Documentation comments
Prefer self-documenting code; comment for the two cases that earn it:
- Public APIs — JSDoc for every exported symbol and option, written for users who can't see the implementation.
- Non-obvious why — workarounds, ordering constraints, native-bridge quirks, RN-version differences. The reasoning, not the play-by-play.
Don't narrate obvious behavior or restate the code. Use @deprecated, @internal, and @hidden deliberately — @internal/@hidden keep a symbol out of the generated API surface even when it must be exported for cross-file use.
Signals
- GitHub stars
- 2k
- Forks
- 367
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
code-guidelines- Source
- github.com/getsentry/sentry-react-native