Skip to content

Cloudflare transport doesn't consume fetch response bodies #18534

Description

@igoforth

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

Framework Version

Cloudflare Workers (Durable Objects)

Link to Sentry event

No events are being sent - that's the issue

Reproduction Example/SDK Setup

// packages/signal/src/index.ts
import * as Sentry from '@sentry/cloudflare';

Sentry.init({
  dsn: 'https://xxx@xxx.ingest.us.sentry.io/xxx',
  tracesSampleRate: 1.0,
  environment: 'production',
});

export default instrumentDurableObjectWithSentry(TradingAgent, {
  dsn: 'https://xxx@xxx.ingest.us.sentry.io/xxx',
  tracesSampleRate: 1.0,
});

Steps to Reproduce

  1. Set up @sentry/cloudflare in a Cloudflare Worker or Durable Object
  2. Configure Sentry with valid DSN
  3. Trigger any Sentry event (error, message, etc.)
  4. Monitor Worker logs with wrangler tail
  5. Observe warnings: "(warn) A stalled HTTP response was canceled to prevent deadlock..."

Expected Result

  • Sentry events should be successfully transmitted to Sentry servers
  • No "stalled HTTP response" warnings in logs
  • Events should appear in Sentry dashboard

Actual Result

Console logs work correctly (visible in wrangler tail), but Sentry events never reach the server.

Logs show repeated warnings:

(warn) A stalled HTTP response was canceled to prevent deadlock. This can happen when a Worker calls fetch() or cache.match() several times without reading the bodies of the returned Response objects.

Root Cause

The Cloudflare transport implementation at packages/cloudflare/src/transport.ts performs a fetch() to send events but never consumes the response body.

Cloudflare Workers runtime requires ALL fetch response bodies to be either:

  1. Read via response.text(), response.json(), response.arrayBuffer(), etc.
  2. Explicitly canceled via response.body?.cancel()

Otherwise, the runtime cancels stalled HTTP connections to prevent deadlock.

Problematic code pattern:

const response = await (options.fetch ?? fetch)(options.url, requestOptions);

return {
  statusCode: response.status,
  headers: {
    'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
    'retry-after': response.headers.get('Retry-After'),
  },
};
// ❌ response.body is never consumed!

Suggested Fix

Add response body consumption after the fetch:

const response = await (options.fetch ?? fetch)(options.url, requestOptions);

// Consume the response body to prevent Cloudflare from canceling the request
// We don't need the body content, but we must read or cancel it
await response.text().catch(() => {}); // or response.body?.cancel()

return {
  statusCode: response.status,
  headers: {
    'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
    'retry-after': response.headers.get('Retry-After'),
  },
};

Additional Context

This issue affects all Cloudflare Workers deployments using @sentry/cloudflare, including:

  • Cloudflare Workers
  • Cloudflare Durable Objects
  • Cloudflare Pages Functions

The problem is specific to the Cloudflare Workers runtime, which enforces stricter fetch response handling than browser or Node.js environments.

Workaround: Currently using wrangler tail for live logging instead of Sentry event transmission.

Related Cloudflare Documentation:

Priority

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions