Skip to content

fix(core): Sample errors after beforeSend while preserving session updates - #22671

Open
s1gr1d wants to merge 5 commits into
developfrom
sig/fix-session-error-rate
Open

fix(core): Sample errors after beforeSend while preserving session updates#22671
s1gr1d wants to merge 5 commits into
developfrom
sig/fix-session-error-rate

Conversation

@s1gr1d

@s1gr1d s1gr1d commented Jul 27, 2026

Copy link
Copy Markdown
Member

When sampleRate causes an error event to be dropped, the _processEvent method returned early before reaching _updateSessionFromEvent, so the session is never marked crashed/errored. This increased crash-free session rates.

This PR moves error event sampling after event processors and beforeSend, while keeping the session update before the sampling decision.

The updated pipeline is:
event processors → beforeSend → session update → sampleRate → send

This follows the session update filtering spec.

Closes #22615

@s1gr1d
s1gr1d requested a review from a team as a code owner July 27, 2026 10:29
@s1gr1d
s1gr1d requested review from Lms24, logaretm and msonnb and removed request for a team, logaretm and msonnb July 27, 2026 10:29
@s1gr1d
s1gr1d force-pushed the sig/fix-session-error-rate branch from e0abd98 to b002e45 Compare July 27, 2026 10:40
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 29.85 kB -0.02% -4 B 🔽
@sentry/browser - with treeshaking flags 28.05 kB -0.03% -7 B 🔽
@sentry/browser (incl. Tracing) 47.42 kB -0.02% -5 B 🔽
@sentry/browser (incl. Tracing + Span Streaming) 47.43 kB -0.02% -5 B 🔽
@sentry/browser (incl. Tracing, Profiling) 52.16 kB -0.02% -6 B 🔽
@sentry/browser (incl. Tracing, Replay) 86.74 kB -0.01% -3 B 🔽
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 76.16 kB -0.02% -8 B 🔽
@sentry/browser (incl. Tracing, Replay with Canvas) 91.48 kB -0.01% -3 B 🔽
@sentry/browser (incl. Tracing, Replay, Feedback) 104.12 kB -0.01% -3 B 🔽
@sentry/browser (incl. Feedback) 47.16 kB -0.01% -4 B 🔽
@sentry/browser (incl. sendFeedback) 34.69 kB -0.02% -4 B 🔽
@sentry/browser (incl. FeedbackAsync) 39.78 kB -0.01% -2 B 🔽
@sentry/browser (incl. Metrics) 30.92 kB -0.01% -2 B 🔽
@sentry/browser (incl. Logs) 31.15 kB -0.02% -5 B 🔽
@sentry/browser (incl. Metrics & Logs) 31.83 kB -0.02% -5 B 🔽
@sentry/react 31.63 kB -0.03% -7 B 🔽
@sentry/react (incl. Tracing) 49.64 kB -0.02% -7 B 🔽
@sentry/vue 34.76 kB -0.03% -9 B 🔽
@sentry/vue (incl. Tracing) 49.37 kB -0.03% -10 B 🔽
@sentry/svelte 29.88 kB -0.01% -2 B 🔽
CDN Bundle 31.89 kB -0.03% -8 B 🔽
CDN Bundle (incl. Tracing) 47.77 kB -0.01% -3 B 🔽
CDN Bundle (incl. Logs, Metrics) 33.44 kB -0.03% -8 B 🔽
CDN Bundle (incl. Tracing, Logs, Metrics) 49.15 kB -0.01% -2 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) 72.79 kB -0.02% -8 B 🔽
CDN Bundle (incl. Tracing, Replay) 85.4 kB -0.01% -7 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 86.68 kB -0.01% -4 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) 91.17 kB -0.01% -3 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 92.47 kB -0.01% -3 B 🔽
CDN Bundle - uncompressed 95.13 kB -0.02% -11 B 🔽
CDN Bundle (incl. Tracing) - uncompressed 143.27 kB -0.01% -11 B 🔽
CDN Bundle (incl. Logs, Metrics) - uncompressed 99.84 kB -0.02% -11 B 🔽
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 147.25 kB -0.01% -11 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 224.6 kB -0.01% -11 B 🔽
CDN Bundle (incl. Tracing, Replay) - uncompressed 262.53 kB -0.01% -11 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 266.49 kB -0.01% -11 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 276.23 kB -0.01% -11 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 280.19 kB -0.01% -11 B 🔽
@sentry/nextjs (client) 52.23 kB -0.02% -7 B 🔽
@sentry/sveltekit (client) 47.84 kB -0.03% -10 B 🔽
@sentry/core/server 79.73 kB +0.01% +1 B 🔺
@sentry/core/browser 51.61 kB +0.01% +1 B 🔺
@sentry/node 122.34 kB -0.01% -11 B 🔽
@sentry/node/import (ESM hook with diagnostics-channel injection) 166 B - -
@sentry/node - without tracing 85.75 kB -0.01% -7 B 🔽
@sentry/aws-serverless 93.77 kB -0.02% -11 B 🔽
@sentry/cloudflare (withSentry) - minified 197.25 kB -0.01% -9 B 🔽
@sentry/cloudflare (withSentry) 485.26 kB -0.01% -30 B 🔽

View base workflow run

@Lms24 Lms24 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.

The sampling fix is correct. For beforeSend, we shouldn't increase error counts for filtered events (see comment).

Comment thread packages/browser/src/helpers.ts Outdated
// Mechanism is passed via hint so it's available even when the event
// is dropped before event processors run (e.g. sampleRate sampling).
// We also add it via an event processor above.
captureException(ex, {

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.

q: given the change in client.ts where we also merge the hint's mechanism: do we need this?

@s1gr1d s1gr1d Jul 27, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, we need it here for when _prepareEvent is skipped.

eventFromException defaults every exception to handled: true, and e.g. handled: false (from the global error handler) is only merged onto the event inside _prepareEvent, which is skipped on the sampleRate drop path -> removing this also breaks this test "marks session as unhandled when unhandled error is sampled out by sampleRate"

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.

ah thanks for explaining, makes sense!

Comment thread packages/core/src/client.ts Outdated
Comment on lines +1497 to +1501
// Reflects crashes inside release health sessions, regardless of beforeSend dropping the event.
const session = currentScope.getSession() || isolationScope.getSession();
if (isError && session) {
this._updateSessionFromEvent(session, preparedEvent);
}

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.

h: events dropped via beforeSend should not update a session. See Sessions spec and the internal slack convo we had about this topic a few weeks ago (will send you the link).

Comment thread packages/core/src/client.ts Outdated
Comment thread packages/core/test/lib/client.test.ts Outdated
const client = new TestClient(options);
setCurrentClient(client);
describe('edge case: sampled-out error that beforeSend would have filtered', () => {
test('updates session even though beforeSend would have dropped the error', () => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

tests the edge-case that was mentioned in a Slack convo (Sampled out + would have been filtered by beforeSend)

@s1gr1d s1gr1d changed the title fix(core): Update session status before event is dropped or beforeSend returns null fix(core): Update session status before sampleRate drops error event Jul 27, 2026
@s1gr1d
s1gr1d requested a review from Lms24 July 27, 2026 12:24

@Lms24 Lms24 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.

thanks for making the changes!

@s1gr1d s1gr1d changed the title fix(core): Update session status before sampleRate drops error event fix(core): Sample errors after beforeSend while preserving session updates Jul 28, 2026
Comment on lines +1483 to +1485
`Discarding event because it's not included in the random sample (sampling rate = ${sampleRate})`,
);
}

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.

Bug: Sentry.lastEventId() is now set for events that are dropped by sampleRate, breaking user feedback integrations like showReportDialog() which rely on a sent event.
Severity: MEDIUM

Suggested Fix

Move the setLastEventId call to after the sampleRate check within the _prepareEvent function. This ensures Sentry.lastEventId() is only set for events that are actually sent, restoring the expected behavior for features that rely on it.

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: packages/core/src/client.ts#L1483-L1485

Potential issue: The `setLastEventId` call in `_prepareEvent` now executes before the
`sampleRate` check. Consequently, `Sentry.lastEventId()` stores the ID for all error
events, including those that are subsequently dropped due to sampling and never sent to
Sentry. This behavior breaks integrations like `showReportDialog()`, which depend on
`lastEventId()` to reference an event that was successfully transmitted in order to
associate user feedback with it.

@s1gr1d
s1gr1d force-pushed the sig/fix-session-error-rate branch from fc24c42 to f711e80 Compare July 28, 2026 12:53
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.

sampleRate for error events silently prevents crash-free session marking

2 participants