You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Sentry.metrics.* triggers setTimeout outside request context in Cloudflare Workers #20888
Sentry.metrics.* (count, gauge, distribution) is exported and enabled by default in @sentry/cloudflare, but capturing a metric triggers a setTimeout(..., 5000) that fires outside the request context, causing a Cloudflare Workers runtime error.
This is particularly visible when bundling with @cloudflare/vite-plugin (which applies stricter runtime checks than older Wrangler builds).
Root Cause
The core client's setupWeightBasedFlushing (packages/core/src/client.ts:105-158) registers an afterCaptureMetric hook that schedules a 5-second setTimeout (DEFAULT_FLUSH_INTERVAL = 5000) to batch-flush the metric envelope. In Cloudflare Workers, this timer fires after the request handler returns, violating the request-scoped execution model.
The CloudflareClient.flush() method handles pending spans but does not address the metric flush timer created by the base client.
Current Workarounds
Disable standalone metrics and use span attributes/span metrics instead:
Sentry.init({enableMetrics: false});
Call Sentry.flush() at the end of every request — this triggers the flushMetrics hook which clears the timer, but the timer still gets created before being cancelled.
Possible Fixes
Make DEFAULT_FLUSH_INTERVAL configurable (e.g. metricsFlushInterval option), allowing @sentry/cloudflare to set it to 0 for synchronous flushing.
Have @sentry/cloudflare override the flushing strategy to flush synchronously within the request context rather than relying on background timers.
At minimum, document that Sentry.metrics.* is not fully supported in Workers and recommend span metrics.
Context
Related to [Metrics] Support cloudflare workers #11220 (closed in 2024 as "won't do" when the old metrics.increment()/metrics.set() API was replaced with span-attribute-based metrics). However, Application Metrics (Sentry.metrics.count/gauge/distribution) is a newer product that shipped after that decision, so the closure rationale no longer fully applies.
Closing this — after tracing through the code, withSentry creates the client inside the request handler (wrapRequestHandler → init()), so the setTimeout from metric capture runs within request context. The timer is also cleared by flushAndDispose() at end of request via waitUntil.
We need the actual stack trace from the original reporter to understand what's happening in their setup. Reopening if/when we get that.
Reopening with updated understanding. The setTimeout from setupWeightBasedFlushing is called within request context (inside the afterCaptureMetric hook), but workerd rejects it when the Worker is built with @cloudflare/vite-plugin using no_bundle: true + native ESM multi-module output. The same code works with Wrangler's single-bundle output.
Stack trace from production confirms the timer originates from the afterCaptureMetric hook → client.emit → setTimeout:
This likely also affects afterCaptureLog (same setupWeightBasedFlushing mechanism).
Proposed fix: Add a _flushInterval client option. Set to 0 in @sentry/cloudflare (and @sentry/vercel-edge) to skip the timer entirely. Size-based flush (800KB) and explicit flush() at end of request (via withSentry → flushAndDispose) still work. Zero performance impact since each request creates its own client.
Activity
linear-code commentedon May 14, 2026
JS-2472
sergical commentedon May 14, 2026
Closing this — after tracing through the code,
withSentrycreates the client inside the request handler (wrapRequestHandler→init()), so thesetTimeoutfrom metric capture runs within request context. The timer is also cleared byflushAndDispose()at end of request viawaitUntil.We need the actual stack trace from the original reporter to understand what's happening in their setup. Reopening if/when we get that.
sergical commentedon May 14, 2026
Reopening with updated understanding. The
setTimeoutfromsetupWeightBasedFlushingis called within request context (inside theafterCaptureMetrichook), but workerd rejects it when the Worker is built with@cloudflare/vite-pluginusingno_bundle: true+ native ESM multi-module output. The same code works with Wrangler's single-bundle output.Stack trace from production confirms the timer originates from the
afterCaptureMetrichook →client.emit→setTimeout:This likely also affects
afterCaptureLog(samesetupWeightBasedFlushingmechanism).Proposed fix: Add a
_flushIntervalclient option. Set to0in@sentry/cloudflare(and@sentry/vercel-edge) to skip the timer entirely. Size-based flush (800KB) and explicitflush()at end of request (viawithSentry→flushAndDispose) still work. Zero performance impact since each request creates its own client.logaretm commentedon May 14, 2026
@JPeer264 Can u take a look at this and #20889
fix(cloudflare, vercel-edge): Disable timer-based flush for serverles…
fix(cloudflare, vercel-edge): Disable timer-based flush for serverles…
github-actions commentedon May 26, 2026
A PR closing this issue has just been released 🚀
This issue was referenced by PR #20889, which was included in the 10.54.0 release.