Skip to content

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

Merged
s1gr1d merged 8 commits into
developfrom
sig/fix-session-error-rate
Jul 29, 2026
Merged

fix(core): Sample errors after beforeSend while preserving session updates#22671
s1gr1d merged 8 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.82 kB -0.02% -4 B 🔽
@sentry/browser - with treeshaking flags 28.02 kB -0.03% -7 B 🔽
@sentry/browser (incl. Tracing) 47.1 kB -0.02% -6 B 🔽
@sentry/browser (incl. Tracing + Span Streaming) 47.1 kB -0.02% -5 B 🔽
@sentry/browser (incl. Tracing, Profiling) 51.81 kB -0.02% -6 B 🔽
@sentry/browser (incl. Tracing, Replay) 86.4 kB -0.02% -13 B 🔽
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 75.84 kB -0.01% -7 B 🔽
@sentry/browser (incl. Tracing, Replay with Canvas) 91.13 kB -0.02% -11 B 🔽
@sentry/browser (incl. Tracing, Replay, Feedback) 103.76 kB -0.02% -13 B 🔽
@sentry/browser (incl. Feedback) 47.13 kB -0.01% -4 B 🔽
@sentry/browser (incl. sendFeedback) 34.66 kB -0.02% -5 B 🔽
@sentry/browser (incl. FeedbackAsync) 39.76 kB -0.01% -2 B 🔽
@sentry/browser (incl. Metrics) 30.89 kB -0.01% -3 B 🔽
@sentry/browser (incl. Logs) 31.12 kB -0.02% -4 B 🔽
@sentry/browser (incl. Metrics & Logs) 31.8 kB -0.02% -5 B 🔽
@sentry/react 31.61 kB -0.02% -6 B 🔽
@sentry/react (incl. Tracing) 49.33 kB -0.02% -7 B 🔽
@sentry/vue 34.74 kB -0.03% -8 B 🔽
@sentry/vue (incl. Tracing) 49.05 kB -0.03% -10 B 🔽
@sentry/svelte 29.85 kB -0.02% -5 B 🔽
CDN Bundle 31.86 kB -0.04% -11 B 🔽
CDN Bundle (incl. Tracing) 47.45 kB +0.01% +1 B 🔺
CDN Bundle (incl. Logs, Metrics) 33.41 kB -0.03% -9 B 🔽
CDN Bundle (incl. Tracing, Logs, Metrics) 48.82 kB -0.01% -3 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) 72.78 kB -0.02% -8 B 🔽
CDN Bundle (incl. Tracing, Replay) 85.09 kB -0.01% -5 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 86.38 kB -0.01% -3 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) 90.85 kB -0.01% -4 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 92.16 kB -0.01% -2 B 🔽
CDN Bundle - uncompressed 95.06 kB -0.02% -11 B 🔽
CDN Bundle (incl. Tracing) - uncompressed 142.28 kB -0.01% -11 B 🔽
CDN Bundle (incl. Logs, Metrics) - uncompressed 99.78 kB -0.02% -11 B 🔽
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 146.26 kB -0.01% -11 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 224.53 kB -0.01% -11 B 🔽
CDN Bundle (incl. Tracing, Replay) - uncompressed 261.54 kB -0.01% -11 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 265.5 kB -0.01% -11 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 275.24 kB -0.01% -11 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 279.2 kB -0.01% -11 B 🔽
@sentry/nextjs (client) 51.92 kB -0.02% -8 B 🔽
@sentry/sveltekit (client) 47.51 kB -0.03% -11 B 🔽
@sentry/core/server 79.59 kB +0.01% +3 B 🔺
@sentry/core/browser 51.6 kB +0.01% +4 B 🔺
@sentry/node 121.68 kB -0.01% -9 B 🔽
@sentry/node/import (ESM hook with diagnostics-channel injection) 166 B - -
@sentry/node - without tracing 85.26 kB -0.02% -9 B 🔽
@sentry/aws-serverless 93.48 kB -0.01% -9 B 🔽
@sentry/cloudflare (withSentry) - minified 197.59 kB -0.01% -9 B 🔽
@sentry/cloudflare (withSentry) 485.72 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.

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.

this is a known limitation. I made it more obvious: b653230

@s1gr1d
s1gr1d force-pushed the sig/fix-session-error-rate branch from fc24c42 to f711e80 Compare July 28, 2026 12:53
@s1gr1d
s1gr1d enabled auto-merge (squash) July 29, 2026 11:46
Comment on lines +1487 to +1490
throw _makeDoNotSendEventError(
`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: The sampleRate check is now performed after event processing, causing unnecessary work for events that are ultimately sampled out and discarded.
Severity: MEDIUM

Suggested Fix

This change was intentional to support session tracking. A potential improvement could be a two-phase processing system: handle session-relevant data, make the sampling decision, and only then execute the full, expensive processing pipeline for events that will be sent, if the spec allows.

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#L1487-L1490

Potential issue: The order of operations for event capturing has been changed to `event
processors → beforeSend → session update → sampleRate → send`. This means that for any
event that is ultimately dropped due to sampling, the SDK still performs all the
processing work upfront. This includes merging scope data, running integrations,
executing potentially expensive user-defined `event processors` and `beforeSend`
callbacks, and data normalization. This introduces a performance regression,
particularly impactful for applications with low `sampleRate` values, where the majority
of events are processed only to be discarded.

@s1gr1d
s1gr1d merged commit f6e91d8 into develop Jul 29, 2026
271 checks passed
@s1gr1d
s1gr1d deleted the sig/fix-session-error-rate branch July 29, 2026 12:02
s1gr1d added a commit that referenced this pull request Jul 29, 2026
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