Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/cloudflare
SDK Version
10.62.0
Framework Version
wrangler 4.113.0, compatibility_date 2026-06-16, compatibility_flags = ["nodejs_compat"]
Link to Sentry event
(events never arrive — that is the bug; org stats show 0 received / 0 filtered for the affected captures)
Reproduction Example/SDK Setup
Durable Object instrumented with instrumentDurableObjectWithSentry. The DO's fetch handler kicks off background work that outlives the invocation — a common DO pattern (queue drains, agent runloops; e.g. the agents SDK runs "fibers" exactly like this):
class RoomBase extends DurableObject {
async fetch(request) {
// fire-and-forget continuation (same shape as agents-SDK fibers)
void (async () => {
await new Promise((r) => setTimeout(r, 3000));
Sentry.captureException(new Error("captured from detached work"));
})();
return Response.json({ ok: true });
}
}
export const Room = instrumentDurableObjectWithSentry(sentryOptions, RoomBase);
Deployed verbatim (minus names) and verified: the event never arrives — not delayed, never. captureException inside the detached continuation is a silent no-op. Captures from the fetch handler itself, and even from ctx.waitUntil-registered work, arrive fine in the same worker.
Steps to Reproduce
- Instrument a DO with
instrumentDurableObjectWithSentry.
- From
fetch, start a detached async continuation (not awaited, not passed to ctx.waitUntil) that captures an exception after the invocation settles.
- Trigger the fetch.
Expected Result
The event is delivered, or at minimum the SDK surfaces (debug log) that a capture hit a disposed client so the drop isn't silent.
Actual Result
Nothing is sent and nothing is logged. Mechanism, as far as we can tell from reading the SDK:
wrapRequestHandler / wrapMethodWithSentry create a per-invocation client and dispose it via waitUntil(flushAndDispose(client)) when the invocation settles.
- The detached continuation inherits the invocation's isolation scope through AsyncLocalStorage, so later
captureException calls run against the disposed client.
makeCloudflareTransport uses IsolatedPromiseBuffer, so events are only ever transmitted on flush() — any capture that misses its scope's flush window is unrecoverable.
Real-world impact: our production DO (a chat agent on the cloudflare agents SDK, where every turn runs as a detached fiber) reported zero errors for 13 days — every captureException from turn code was dropped — while the same code paths kept logging to Workers Logs. Nothing in the SDK hinted at the drop; we found it by diffing org ingest stats (0 received) against console output.
Two follow-up observations from building a workaround, in case they're useful:
- Workaround that works: wrap the detached body in
wrapRequestHandler({ options, request: new Request("https://do.detached/<name>"), context: this.ctx }, body) to give it a body-scoped client. However, in our production DO the wrapper's own teardown flush (waitUntil(flushAndDispose)) still did not deliver — we additionally had to drain the transport explicitly inside the handler (getClient()?.getTransport()?.flush(...)) before it settles. In a minimal DO the teardown path delivered fine, so there seems to be a further interaction (flush-lock vs. long-lived ctx.waitUntil registrations?) we could not minimize. Happy to run diagnostics if useful.
- While doing that we also noticed
CloudflareClient.flush() awaits pending-span completion; calling it from inside an active request span therefore always stalls for the full timeout — worth a doc note, since flushing "before the invocation ends" is exactly what a detached-work workaround wants to do.
Feature-ask version of this issue: a supported primitive for detached/background DO work (e.g. Sentry.withDetachedScope(name, fn) that binds a body-scoped client and flushes on settle), plus a debug-mode warning when captureException resolves to a disposed client.
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/cloudflare
SDK Version
10.62.0
Framework Version
wrangler 4.113.0, compatibility_date 2026-06-16, compatibility_flags = ["nodejs_compat"]
Link to Sentry event
(events never arrive — that is the bug; org stats show 0 received / 0 filtered for the affected captures)
Reproduction Example/SDK Setup
Durable Object instrumented with
instrumentDurableObjectWithSentry. The DO's fetch handler kicks off background work that outlives the invocation — a common DO pattern (queue drains, agent runloops; e.g. theagentsSDK runs "fibers" exactly like this):Deployed verbatim (minus names) and verified: the event never arrives — not delayed, never.
captureExceptioninside the detached continuation is a silent no-op. Captures from the fetch handler itself, and even fromctx.waitUntil-registered work, arrive fine in the same worker.Steps to Reproduce
instrumentDurableObjectWithSentry.fetch, start a detached async continuation (not awaited, not passed toctx.waitUntil) that captures an exception after the invocation settles.Expected Result
The event is delivered, or at minimum the SDK surfaces (debug log) that a capture hit a disposed client so the drop isn't silent.
Actual Result
Nothing is sent and nothing is logged. Mechanism, as far as we can tell from reading the SDK:
wrapRequestHandler/wrapMethodWithSentrycreate a per-invocation client and dispose it viawaitUntil(flushAndDispose(client))when the invocation settles.captureExceptioncalls run against the disposed client.makeCloudflareTransportusesIsolatedPromiseBuffer, so events are only ever transmitted onflush()— any capture that misses its scope's flush window is unrecoverable.Real-world impact: our production DO (a chat agent on the cloudflare
agentsSDK, where every turn runs as a detached fiber) reported zero errors for 13 days — everycaptureExceptionfrom turn code was dropped — while the same code paths kept logging to Workers Logs. Nothing in the SDK hinted at the drop; we found it by diffing org ingest stats (0 received) against console output.Two follow-up observations from building a workaround, in case they're useful:
wrapRequestHandler({ options, request: new Request("https://do.detached/<name>"), context: this.ctx }, body)to give it a body-scoped client. However, in our production DO the wrapper's own teardown flush (waitUntil(flushAndDispose)) still did not deliver — we additionally had to drain the transport explicitly inside the handler (getClient()?.getTransport()?.flush(...)) before it settles. In a minimal DO the teardown path delivered fine, so there seems to be a further interaction (flush-lock vs. long-livedctx.waitUntilregistrations?) we could not minimize. Happy to run diagnostics if useful.CloudflareClient.flush()awaits pending-span completion; calling it from inside an active request span therefore always stalls for the full timeout — worth a doc note, since flushing "before the invocation ends" is exactly what a detached-work workaround wants to do.Feature-ask version of this issue: a supported primitive for detached/background DO work (e.g.
Sentry.withDetachedScope(name, fn)that binds a body-scoped client and flushes on settle), plus a debug-mode warning whencaptureExceptionresolves to a disposed client.