Skip to content

fix(browser): End the active idle span on pagehide - #23779

Open
logaretm wants to merge 1 commit into
developfrom
awad/pagehide-idle-span
Open

fix(browser): End the active idle span on pagehide#23779
logaretm wants to merge 1 commit into
developfrom
awad/pagehide-idle-span

Conversation

@logaretm

@logaretm logaretm commented Aug 31, 2026

Copy link
Copy Markdown
Member

Ends the active idle span on pagehide, so a navigation span that is still open when the user leaves the page doesn't lose its root.

A navigation span whose idle timeout has not elapsed when the document
goes away was reaching Sentry as a rootless trace: its children had been
streamed while the page was alive, but the root span never arrived.

`registerBackgroundTabDetection` is meant to be the safety net, but it
listens for `visibilitychange`. Measured on a same-tab cross-document
navigation in Chrome 152, the order is:

    pagehide (persisted=true)  ->  visibilitychange (hidden)

`pagehide` comes first and freezes the document into the bfcache, so a
root span ended on `visibilitychange` is ended on a page that can no
longer send, and it is stranded on its own. That is also why the
cancellation never appears in the debug log: it is logged from a frozen
document.

`pagehide` is the last point at which a document can still send, so the
idle span is ended there instead, which also puts the root in the same
final batch as the remaining children. The trace then arrives whole or
not at all, rather than headless.

Verified against a real project: rootless traces appear without this and
consistently do not with it. Trace delivery volume is unchanged (7-8 of
10 traces over three runs each way), which is expected - this changes
whether the root travels with its children, not how much gets through.

The traces that go missing entirely are a separate problem: four envelope
requests per run are killed with `net::ERR_ABORTED` during unload, which
points at the `keepalive` budget in `fetch.ts` rather than at span
lifecycle.
@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 28.56 kB - -
@sentry/browser - with treeshaking flags 26.92 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 26.82 kB - -
@sentry/browser (incl. Tracing) 48.78 kB +0.06% +29 B 🔺
@sentry/browser (incl. Tracing + Span Streaming) 48.79 kB +0.06% +29 B 🔺
@sentry/browser (incl. Tracing, Profiling) 51.71 kB +0.06% +31 B 🔺
@sentry/browser (incl. Tracing, Replay) 88.26 kB +0.04% +30 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 77.65 kB +0.04% +26 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 92.97 kB +0.04% +34 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 105.89 kB +0.04% +33 B 🔺
@sentry/browser (incl. Feedback) 46.05 kB - -
@sentry/browser (incl. sendFeedback) 33.62 kB - -
@sentry/browser (incl. FeedbackAsync) 38.73 kB - -
@sentry/browser (incl. Metrics) 29.51 kB - -
@sentry/browser (incl. Logs) 29.8 kB - -
@sentry/browser (incl. Metrics & Logs) 30.43 kB - -
@sentry/react 30.3 kB - -
@sentry/react (incl. Tracing) 50.97 kB +0.06% +29 B 🔺
@sentry/vue 35.73 kB - -
@sentry/vue (incl. Tracing) 51.04 kB +0.06% +27 B 🔺
@sentry/svelte 28.59 kB - -
CDN Bundle 30.35 kB - -
CDN Bundle (incl. Tracing) 49.41 kB +0.07% +32 B 🔺
CDN Bundle (incl. Logs, Metrics) 32.58 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 51.28 kB +0.05% +24 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 73.17 kB - -
CDN Bundle (incl. Tracing, Replay) 86.89 kB +0.04% +28 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 88.76 kB +0.04% +29 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 92.82 kB +0.03% +23 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 94.67 kB +0.03% +24 B 🔺
CDN Bundle - uncompressed 89.95 kB - -
CDN Bundle (incl. Tracing) - uncompressed 147.32 kB +0.09% +125 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 96.24 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 153.01 kB +0.09% +125 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 225.41 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 266.82 kB +0.05% +125 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 272.5 kB +0.05% +125 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 280.52 kB +0.05% +125 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 286.19 kB +0.05% +125 B 🔺
@sentry/nextjs (client) 53.59 kB +0.06% +27 B 🔺
@sentry/sveltekit (client) 49.22 kB +0.07% +33 B 🔺
@sentry/core/server 65.67 kB - -
@sentry/core/browser 51.86 kB - -
@sentry/node 123.77 kB +0.02% +18 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 85.53 kB - -
@sentry/node - without tracing 88.03 kB +0.02% +16 B 🔺
@sentry/node - without channel injection 103.18 kB +0.03% +23 B 🔺
@sentry/aws-serverless 96.42 kB +0.03% +27 B 🔺
@sentry/cloudflare (withSentry) - minified 201.04 kB - -
@sentry/cloudflare (withSentry) 500.26 kB - -

View base workflow run

@logaretm
logaretm marked this pull request as ready for review August 31, 2026 16:16
@logaretm
logaretm requested a review from a team as a code owner August 31, 2026 16:16
@logaretm
logaretm requested review from Lms24 and msonnb and removed request for a team August 31, 2026 16:16

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

Looks reasonable to me! The interesting part is that the visibilityChange listener that also ends the span is gated by the markBackgroundSpan.

Which tbh, I'm a bit torn if we should keep supporting long-term because to me it seems like we'd always want to cancel and end a span when going to background. It's also a bit weird that the visibilityChange logic sets an error status. But this decision is out of scope of this PR I'd say.

So I'd suggest, for now we also gate on markBackgroundSpan, wdyt?

WINDOW.addEventListener?.('pagehide', () => {
const activeSpan = getActiveIdleSpan(client);
if (activeSpan && !spanToJSON(activeSpan).end_timestamp) {
activeSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, 'documentHidden');

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.

super-l: we can use the @sentry/conventions constant here instead

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