Problem
A user beforeSend / beforeBreadcrumb / beforeSendLog callback that triggers another SDK capture — directly, or transitively through a logging integration that routes back into Sentry (Timber's SentryTimberTree, or the Gradle plugin's logcat instrumentation) — recurses until a StackOverflowError. The callbacks run synchronously on the caller thread with only a try/catch; nothing prevents re-entrant capture.
This is the same class of bug as the logcat crash SDK-CRASHES-JAVA-3T3H (fixed per-integration in #5734). Investigation confirmed Timber has it too, and the core offers no protection, so we fix the whole class once in the core.
Fix
SentryCallbackReentrancyGuard: a shared thread-local flag set only around each callback's execute(...). Capture entry points (SentryClient.captureEvent/captureTransaction/captureLog, Scope.addBreadcrumb) drop a nested capture while the flag is active, breaking the loop for every capture type. Event-processor captures run outside the flag and are unaffected.
Behavioral change: a capture a callback makes intentionally is dropped while that callback runs (logged at DEBUG). Dropping rather than sending is deliberate — bypassing beforeSend would risk unscrubbed PII. Makes the per-integration SentryLogcatAdapter guard redundant.
Covered by regression tests in SentryClientTest, ScopeTest, and an end-to-end SentryTimberIntegrationTest.
Links
Problem
A user
beforeSend/beforeBreadcrumb/beforeSendLogcallback that triggers another SDK capture — directly, or transitively through a logging integration that routes back into Sentry (Timber'sSentryTimberTree, or the Gradle plugin's logcat instrumentation) — recurses until aStackOverflowError. The callbacks run synchronously on the caller thread with only atry/catch; nothing prevents re-entrant capture.This is the same class of bug as the logcat crash SDK-CRASHES-JAVA-3T3H (fixed per-integration in #5734). Investigation confirmed Timber has it too, and the core offers no protection, so we fix the whole class once in the core.
Fix
SentryCallbackReentrancyGuard: a shared thread-local flag set only around each callback'sexecute(...). Capture entry points (SentryClient.captureEvent/captureTransaction/captureLog,Scope.addBreadcrumb) drop a nested capture while the flag is active, breaking the loop for every capture type. Event-processor captures run outside the flag and are unaffected.Behavioral change: a capture a callback makes intentionally is dropped while that callback runs (logged at DEBUG). Dropping rather than sending is deliberate — bypassing
beforeSendwould risk unscrubbed PII. Makes the per-integrationSentryLogcatAdapterguard redundant.Covered by regression tests in
SentryClientTest,ScopeTest, and an end-to-endSentryTimberIntegrationTest.Links