Skip to content

fix(android): mark tombstone and ANR exits as reported when the event is dropped - #6002

Open
markushi wants to merge 2 commits into
mainfrom
fix/mark-dropped-app-exits-as-reported
Open

fix(android): mark tombstone and ANR exits as reported when the event is dropped#6002
markushi wants to merge 2 commits into
mainfrom
fix/mark-dropped-app-exits-as-reported

Conversation

@markushi

Copy link
Copy Markdown
Member

📜 Description

The last reported marker (last_tombstone_report / last_anr_report) was only written as a side
effect of caching the envelope on disk. An event dropped by beforeSend never gets there, so the
same ApplicationExitInfo was turned into an event again at every app start.

ApplicationExitInfoHistoryDispatcher now writes the marker as well when captureEvent returns
SentryId.EMPTY_ID, through a new ApplicationExitInfoPolicy.markReported(long). The successful
path is unchanged.

💡 Motivation and Context

A discarded crash must stay discarded. This is how signal handler events already behave, because
OutboxSender deletes the outbox file independent of the result of beforeSend.

💚 How did you test it?

New test in ApplicationExitIntegrationTestBase, so it runs for both TombstoneIntegrationTest
and AnrV2IntegrationTest. It failed before the change and passes now. Full
:sentry-android-core:testReleaseUnitTest is green.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the 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.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

🔮 Next steps

TombstonePolicy deletes the matching native outbox file before the capture, so native data cannot
come back if the merged event is lost. That is a separate defect.

… is dropped

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 26, 2026

Copy link
Copy Markdown

JAVA-697

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sentry

sentry Bot commented Aug 26, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.53.0 (1) release

⚙️ sentry-android Build Distribution Settings

@markushi
markushi marked this pull request as ready for review August 26, 2026 10:01
Comment on lines 188 to +195
final @NotNull SentryId sentryId = scopes.captureEvent(report.getEvent(), report.getHint());
final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID);
if (!isEventDropped) {
if (isEventDropped) {
// A dropped event never reaches the envelope disk cache, which is where the last reported
// marker is normally written. Without writing it here, the very same exit would be turned
// into an event again on the next app start, ignoring the user's decision to drop it.
policy.markReported(exitInfo.getTimestamp());
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The code marks an exit event as reported if captureEvent returns SentryId.EMPTY_ID. This ID is also returned for transient I/O errors, causing events to be permanently lost instead of retried.
Severity: HIGH

Suggested Fix

The captureEvent method should provide a way to distinguish between intentional drops and transient failures. For example, it could throw a specific exception for I/O errors instead of returning SentryId.EMPTY_ID. The calling code in ApplicationExitInfoHistoryDispatcher should then catch this exception and avoid calling policy.markReported(), allowing the event to be retried later.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoHistoryDispatcher.java#L188-L195

Potential issue: The `captureEvent` method returns `SentryId.EMPTY_ID` for both
intentional event drops (e.g., via `beforeSend` or sampling) and transient failures like
an `IOException` during envelope processing. The new logic in
`ApplicationExitInfoHistoryDispatcher` treats any `SentryId.EMPTY_ID` return as an
intentional drop and calls `policy.markReported()`. This means if an application exit
event fails to be captured due to a temporary network or I/O issue, it will be
permanently marked as reported and will not be retried on the next application start,
leading to the loss of critical ANR or crash data.

Did we get this right? 👍 / 👎 to inform future reviews.

@markushi markushi added the sanity-check PR needs a lightweight review for obvious issues label Aug 26, 2026

@0xadam-brown 0xadam-brown left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice to fix this 👍

Lgtm save for the ambiguity of the EMPTY_ID, as called out by SentryBot here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sanity-check PR needs a lightweight review for obvious issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tombstone events discarded from beforeSend are re-reported on every app launch

2 participants