Skip to content

feat(android): Add internal non-terminating envelope capture #5795

Open
buenaflor wants to merge 11 commits into
mainfrom
feat/pending-unhandled-sessions
Open

feat(android): Add internal non-terminating envelope capture #5795
buenaflor wants to merge 11 commits into
mainfrom
feat/pending-unhandled-sessions

Conversation

@buenaflor

@buenaflor buenaflor commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

📜 Description

Add InternalSentrySdk.captureEnvelopeNonTerminating for hybrid runtimes such as Flutter where an unhandled exception does not terminate the process.

The new path keeps the current session alive with the same SID, increments its error count, persists a pending-unhandled marker in the existing session cache, and finalizes it as unhandled only when the session naturally ends. Existing Abnormal and Crashed terminal states remain authoritative, and the existing captureEnvelope(byte[], boolean) behavior used by other SDKs is unchanged.

💡 Motivation and Context

Flutter currently forwards handled=false events through the terminating hybrid capture path. This marks the session as crashed and may start a replacement session even though the Flutter process continues running, which lowers crash-free session rates incorrectly.

This follows the session protocol's unhandled status for exceptions that are unhandled by the application but do not terminate the process.

💚 How did you test it?

  • Added coverage for pending session mutation and serialization, natural/abnormal/crashed terminal precedence, previous-session recovery, SID-aware cache start/end handling, null SID handling, and terminating capture compatibility.

📝 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

  • Update the Flutter Android bridge to use captureEnvelopeNonTerminating for non-terminating unhandled events.

Made with Cursor

buenaflor and others added 5 commits July 16, 2026 13:53
…-unhandled sessions

Hybrid unhandled exceptions that don't terminate the process (e.g. Flutter)
no longer end the session as crashed. The session is kept alive and marked
pending-unhandled, finalized as unhandled on session end or escalated to
crashed on a native crash. Existing captureEnvelope(byte[], boolean) behavior
(React Native) is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the existing hybrid capture path isolated from non-terminating Flutter
session handling while retaining pending-unhandled session persistence.

Co-authored-by: Cursor <cursoragent@cursor.com>
Serialize pending session mutations with scope and cache lifecycle operations
so delayed session updates cannot overwrite or delete newer session state.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:

  • sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java

Added unreleased section with recent fixes for ANR and crash events.
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 2af8891

@buenaflor

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c6f705c. Configure here.

@sentry

sentry Bot commented Jul 21, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

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

⚙️ sentry-android Build Distribution Settings

@buenaflor

Copy link
Copy Markdown
Contributor Author

@sentry review

Comment thread sentry/src/main/java/io/sentry/cache/EnvelopeCache.java
buenaflor and others added 2 commits July 21, 2026 13:53
Check for an unhandled exception directly instead of using the crash-named
predicate in a path where the process intentionally continues.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 325.76 ms 365.58 ms 39.82 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
eb95ded 317.51 ms 369.08 ms 51.57 ms
2124a46 319.19 ms 415.04 ms 95.85 ms
c3ee041 310.64 ms 361.90 ms 51.26 ms
dcc6bbf 382.58 ms 462.13 ms 79.54 ms
7a19fee 315.46 ms 368.62 ms 53.16 ms
9054d65 330.94 ms 403.24 ms 72.30 ms
d500866 326.13 ms 378.70 ms 52.58 ms
ad8da22 365.86 ms 427.00 ms 61.14 ms
ed33deb 337.52 ms 484.06 ms 146.54 ms
d15471f 310.26 ms 377.04 ms 66.78 ms

App size

Revision Plain With Sentry Diff
eb95ded 0 B 0 B 0 B
2124a46 1.58 MiB 2.12 MiB 551.51 KiB
c3ee041 0 B 0 B 0 B
dcc6bbf 1.58 MiB 2.12 MiB 553.10 KiB
7a19fee 0 B 0 B 0 B
9054d65 1.58 MiB 2.29 MiB 723.38 KiB
d500866 0 B 0 B 0 B
ad8da22 1.58 MiB 2.29 MiB 719.83 KiB
ed33deb 1.58 MiB 2.13 MiB 559.52 KiB
d15471f 1.58 MiB 2.13 MiB 559.54 KiB

Previous results on branch: feat/pending-unhandled-sessions

Startup times

Revision Plain With Sentry Diff
1bf9802 323.61 ms 373.87 ms 50.26 ms
2238594 324.54 ms 372.72 ms 48.18 ms

App size

Revision Plain With Sentry Diff
1bf9802 0 B 0 B 0 B
2238594 0 B 0 B 0 B

Only preserve a mismatched current session when it carries pending unhandled state, retaining existing deletion behavior for other sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
@buenaflor

Copy link
Copy Markdown
Contributor Author

@sentry review

@buenaflor

Copy link
Copy Markdown
Contributor Author

@cursor review

@getsentry getsentry deleted a comment from github-actions Bot Jul 22, 2026
@getsentry getsentry deleted a comment from github-actions Bot Jul 22, 2026
@getsentry getsentry deleted a comment from github-actions Bot Jul 22, 2026
@getsentry getsentry deleted a comment from github-actions Bot Jul 22, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 184e89a. Configure here.

@buenaflor
buenaflor marked this pull request as ready for review July 22, 2026 10:20
Copilot AI review requested due to automatic review settings July 22, 2026 10:20
@buenaflor
buenaflor requested a review from 0xadam-brown as a code owner July 22, 2026 10:20
@buenaflor buenaflor changed the title feat(android): Add non-terminating envelope capture for Flutter feat(android): Add internal non-terminating envelope capture Jul 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a non-terminating “unhandled” session path intended for hybrid runtimes (notably Flutter) where handled=false does not imply a process crash. It introduces a locally persisted “pending unhandled” marker on the active session, so the session can later be finalized as unhandled on natural end (unless escalated to crashed/abnormal).

Changes:

  • Add InternalSentrySdk.captureEnvelopeNonTerminating(byte[]) (Android) to capture envelopes without ending/replacing the session, while persisting a pending-unhandled marker and incrementing session error count.
  • Extend Session to support a pending-unhandled lifecycle (mutation, cloning, serialization) and adjust session end/finalization behavior.
  • Update EnvelopeCache session start/end handling to preserve newer same-SID snapshots and avoid deleting a pending session on mismatched session-end envelopes; add broad test coverage across cache/finalizer/session serialization.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
sentry/src/main/java/io/sentry/Session.java Adds pending-unhandled state tracking, serialization/deserialization, and end()/update() precedence rules.
sentry/src/main/java/io/sentry/Scope.java Exposes Scope.IWithSession as public (still @ApiStatus.Internal) to enable cross-module usage.
sentry/src/main/java/io/sentry/cache/EnvelopeCache.java Improves session start/end disk behavior for same-SID delayed snapshots and pending-unhandled preservation; adds persistCurrentSession.
sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java Adds captureEnvelopeNonTerminating and documents when to use it vs terminating capture.
sentry/src/test/java/io/sentry/SessionTest.kt Adds unit tests for pending-unhandled mutation, end-state precedence, clone behavior, and JSON round-tripping.
sentry/src/test/java/io/sentry/protocol/SessionSerializationTest.kt Adds protocol-level serialization test for unhandled + pending flag round-trip.
sentry/src/test/java/io/sentry/PreviousSessionFinalizerTest.kt Adds recovery/finalization tests for pending-unhandled vs crash-marker precedence.
sentry/src/test/java/io/sentry/cache/EnvelopeCacheTest.kt Adds extensive session file behavior tests for SID matching/mismatch, null SID edge cases, and terminal precedence.
sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt Adds integration-style tests validating non-terminating behavior, persistence, and later finalization/escalation.
sentry/api/sentry.api Updates API surface snapshots for new/changed public ABI elements.
sentry-android-core/api/sentry-android-core.api Updates API snapshot to include captureEnvelopeNonTerminating.
CHANGELOG.md Adds an “Internal” changelog entry describing the new hybrid non-terminating capture API.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 23 to +25
Crashed,
Abnormal
Abnormal,
Unhandled

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

what do ya'll think?

Comment thread sentry/src/test/java/io/sentry/PreviousSessionFinalizerTest.kt Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:

  • sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java

Only retain a mismatched pending session when its start time proves it is newer than the delayed session end.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:

  • sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java

Comment on lines +119 to +133
try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) {
final @Nullable Session endingSession = readSessionFromEnvelope(envelope);
final @Nullable Session currentSession = readSessionFromDisk(currentSessionFile);
final boolean preservePendingSession =
endingSession != null
&& currentSession != null
&& currentSession.isPendingUnhandled()
&& endingSession.getSessionId() != null
&& currentSession.getSessionId() != null
&& !Objects.equals(endingSession.getSessionId(), currentSession.getSessionId())
&& endingSession.getStarted() != null
&& currentSession.getStarted() != null
&& currentSession.getStarted().after(endingSession.getStarted());
if (!preservePendingSession && !currentSessionFile.delete()) {
options.getLogger().log(WARNING, "Current envelope doesn't exist.");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Instead of deleting session.json unconditionally, we preserve it when a delayed End(A) would delete a newer pending-unhandled session B, confirmed by a different SID and later start time. For example, if End(A) is queued before B’s pending-unhandled state is written, processing End(A) must not delete B’s file. All other cases retain the existing deletion behavior, so this currently only affects state persisted by captureEnvelopeNonTerminating().

Comment on lines 21 to 28
Ok,
Exited,
Crashed,
Abnormal
Abnormal,
Unhandled
}

/** started timestamp */

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 new State.Unhandled enum is serialized to "unhandled", an invalid session status for the Sentry relay, causing session data to be rejected.
Severity: HIGH

Suggested Fix

Before serializing the session for the envelope, map the internal State.Unhandled to a valid, recognized session status that the Sentry relay can process, such as Abnormal. Alternatively, coordinate with the server team to have "unhandled" added as a valid status to the session protocol if this is intended to be a new standard state.

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/src/main/java/io/sentry/Session.java#L21-L28

Potential issue: The newly introduced `Session.State.Unhandled` enum value is serialized
to the string `"unhandled"` before being sent to the Sentry relay. The relay's ingestion
pipeline only accepts `ok`, `exited`, `crashed`, and `abnormal` as valid session
statuses. As a result, any session finalized with the `Unhandled` status will be
rejected or mishandled by the server. This leads to the loss of session data for
scenarios involving non-terminating unhandled exceptions, undermining the goal of
improving crash-free session rate calculations. The issue occurs when a session with a
pending unhandled exception is finalized.

Also affects:

  • sentry/src/main/java/io/sentry/Session.java:253~261

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants