Instructions for LLM agents. Keep edits minimal (headers + bullets). Use
/agents-mdskill when editing.
Use Objective-C sparingly; prefer Swift when possible.
Use [[Class alloc] init], not [Class new]:
// Correct
NSMutableArray *items = [[NSMutableArray alloc] init];
SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init];
// Wrong
NSMutableArray *items = [NSMutableArray new];
SentryBreadcrumb *crumb = [SentryBreadcrumb new];- Wrap header files with
NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END - Mark nullable parameters/properties explicitly with
nullable
Prefer protocol-oriented design where it improves testability and composition. See Protocol-Oriented Programming in Swift (WWDC15) and PR #7017.
Pattern — Define multiple protocols with default implementations via extensions. Implementing types adopt several protocols and inherit defaults for free. Protocol constraints on generics reduce required init/method signatures:
protocol ItemProtocol { var id: String { get } }
protocol StorageProtocol<Item> { associatedtype Item; func append(_ item: Item) }
protocol Enricher {
func enrich<Item: ItemProtocol>(_ item: inout Item)
}
extension Enricher {
func enrich<Item: ItemProtocol>(_ item: inout Item) { /* default implementation modifying item */ }
}
// Scope adopts Enricher and gets enrich() for free
extension Scope: Enricher {}
// Generic Buffer: protocols constrain Storage and Item, reducing init surface
final class Buffer<Storage: StorageProtocol, Item: ItemProtocol> {
init(storage: Storage) { /* only needs what protocols require */ }
}- Follow Swift API Design Guidelines
- Use
camelCasefor properties, methods, local variables - Use
PascalCasefor types and protocols - Module-level constants:
camelCase(e.g.,sentryAutoTransactionMaxDuration)
- Default to
internal— only markpublicwhat is part of the SDK's public API - Use
privateoverfileprivateunless sibling types in the same file need access @_spi(Private)for SPI consumed by hybrid SDKs (React Native, Flutter, .NET, Unity)
- Mark classes
finalunless they are explicitly designed for subclassing - Applies to both
internalandpublicclasses
- Never let the SDK crash the host app — wrap all public entry points in
do/catchor equivalent - Prefer
Result<T, Error>or optional returns over throwing for internal APIs - Log errors via
SentryLograther than asserting in production paths
- Always use explicit capture lists when capturing
selfto prevent retain cycles - Prefer
[weak self]withguard let selffor closures stored by the SDK
- Backward compatibility — do not remove or rename public symbols; deprecate first
@objc— all public API must be accessible from ObjC; use@objc(name)orNS_SWIFT_NAMEfor idiomatic naming in both languages@_spi(Private)— internal API for hybrid SDKs; must not appear in public headersPrivateSentrySDKOnly— ObjC SPI class; document instability in headerdocsSENTRY_NO_INIT— use on types that should not be publicly instantiated- Deprecation — add
@available(*, deprecated, message:)with migration guidance
| Primitive | Where |
|---|---|
@synchronized |
SentryScope, SentryHub, SentryClient, SentrySDKInternal, SentryHttpTransport, SentryFileManager |
NSLock |
SentrySessionReplay |
NSRecursiveLock |
SentrySDK.startOptionLock |
pthread_mutex |
SentryCrashBinaryImageCache, SentryCrashReportStore, SentrySwizzle |
dispatch_queue_t |
SentryDispatchQueueWrapper (io.sentry.http-transport, serial, low QoS) |
dispatch_group |
SentryHttpTransport (flush coordination) |
| C11 atomics | SentryCrash monitors (signal-safe context) |
For Swift, use the synchronized extensions in Locks.swift (NSLock, NSRecursiveLock) rather than raw lock calls.
- SDK runs on arbitrary queues — assume any public method can be called from any thread
- Use
@synchronized(self)for ObjC mutable state (scope, hub, client) - Prefer
SentryDispatchQueueWrapperover rawdispatch_queue_t— it's mockable for tests - Never do synchronous work on the main thread in production paths
SentryHubdispatches transaction capture to itsdispatchQueuefor background processing
Located in Sources/SentryCrash/. Fork of KSCrash — when fixing bugs or investigating, check upstream KSCrash for relevant fixes.
Code inside crash/signal handlers must be async-signal-safe:
- No heap allocations (
malloc,calloc,new) — use stack buffers or pre-allocated memory - No locks (
pthread_mutex,@synchronized) — can deadlock if the crash occurred while holding a lock - No ObjC messaging — runtime may be in an inconsistent state
- Allowed:
write(),vsnprintf,strerror_r, C11 atomics,SENTRY_ASYNC_SAFE_LOG_*macros pthread_self()is technically not async-signal-safe but accepted as a known trade-off- See signal-safety(7) and develop docs for context
- Always bounds-check when writing to fixed-size buffers
- Use
snprintf(neversprintf) - Validate all indices before array access
| Component | Purpose |
|---|---|
SentryCrashMonitor_Signal.c |
POSIX signal handler |
SentryCrashMonitor_MachException.c |
Mach exception handler |
SentryCrashMonitor_NSException.m |
ObjC uncaught exception handler |
SentryCrashMonitor_CPPException.cpp |
C++ uncaught exception handler |
SentryCrashReport.c |
Crash report generation |
SentryCrashReportStore.c |
Report persistence |
SentryCrashJSONCodec.c |
JSON encoding (async-signal-safe subset) |
SentryScopeSyncC |
Scope data synced for crash-time access |