Skip to content

fix(core): consume response body in _flush() and _capture() to prevent CF Workers warnings#3211

Closed
jgarrison929 wants to merge 1 commit intoPostHog:mainfrom
jgarrison929:fix/flush-consume-response-body
Closed

fix(core): consume response body in _flush() and _capture() to prevent CF Workers warnings#3211
jgarrison929 wants to merge 1 commit intoPostHog:mainfrom
jgarrison929:fix/flush-consume-response-body

Conversation

@jgarrison929
Copy link

@jgarrison929 jgarrison929 commented Mar 6, 2026

Problem

_flush() and the single-event capture path in posthog-core-stateless.ts both call fetchWithRetry() but discard the returned response without reading the body.

In Cloudflare Workers (and potentially other edge runtimes), an unconsumed response body forces the runtime to clean up the underlying connection later — potentially in a different request's context — which triggers:

Warning: A promise was resolved or rejected from a different request context than the one it was created in.

The runtime may also cancel continuations for the leaked promises, so post-flush error handling may silently not run.

This is the same class of bug the Sentry SDK had and fixed in getsentry/sentry-javascript#18534.

Fix

Consume the response body at all sites where it was previously discarded:

Success paths

  • _flush() (~line 1142) — the batched event flush path
  • _capture() (~line 967) — the single-event capture path (used when flushAt: 1)

Error paths

  • Both catch blocks now also consume the response body held by PostHogFetchHttpError. This covers the 413 batch-size reduction path and other HTTP error paths where the error response was previously discarded.

The fetchWithRetry() return type is unchanged so callers that need the response body (e.g., the surveys API endpoint) can still read it normally.

Note: Intermediate retry attempts inside retriable() may still produce unconsumed error responses during the retry loop. This is a separate concern in the retriable utility and would be best addressed separately if needed.

Changes

  • packages/core/src/posthog-core-stateless.ts — 4 sites updated (+19 lines, -2 lines)

Fixes #3173

@vercel
Copy link

vercel bot commented Mar 6, 2026

@jgarrison929 is attempting to deploy a commit to the PostHog Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 6, 2026

Comments Outside Diff (1)

  1. packages/core/src/posthog-core-stateless.ts, line 1224-1225 (link)

    Error response bodies are also left unconsumed, causing the same Cloudflare Workers warning for HTTP errors.

    When fetchWithRetry throws a PostHogFetchHttpError at line 1225, the response body is stored in the error object via lazy getters (see lines 53-59). However, neither the _capture() catch block (line 972) nor the main _flush() catch block (lines 1151-1170) consume the body. Only logFlushError (line 80) reads it—but that's only invoked from flushBackground(), not from direct flush() calls or from _capture() error paths.

    To consistently fix this for both success and error responses, consider eagerly consuming the response body inside PostHogFetchHttpError at construction time:

    Then update the error class to store the text directly and cache it:

    class PostHogFetchHttpError extends Error {
      name = 'PostHogFetchHttpError'
      readonly responseText: string
    
      constructor(
        public response: PostHogFetchResponse,
        responseText: string,
        public reqByteLength: number
      ) {
        super('HTTP error while fetching PostHog: status=' + response.status + ', reqByteLength=' + reqByteLength)
        this.responseText = responseText
      }
      // ...
    }

    This ensures the body is always consumed at the single point where the error is created, regardless of where or how the error is caught.

Last reviewed commit: 45b2a56

…t Cloudflare Workers warnings

`_flush()` and the single-event `_capture()` path both call
`fetchWithRetry()` but discard the returned response without reading
the body. In runtimes like Cloudflare Workers that enforce response
body consumption, this causes cross-request promise resolution warnings
and may silently cancel post-flush error handling continuations.

Consume the response body with `res.text().catch(() => {})` at both
call sites on the success path. Also consume the response body held by
`PostHogFetchHttpError` on error paths (e.g. 413 batch-size reduction)
to prevent the same leak on failed requests.

The `fetchWithRetry()` return type is preserved so callers that need
the response (e.g. surveys API) can still read it.

Fixes PostHog#3173
@jgarrison929 jgarrison929 force-pushed the fix/flush-consume-response-body branch from 45b2a56 to f7036c0 Compare March 6, 2026 21:07
@jgarrison929
Copy link
Author

Thanks for the review! The latest force-push (f7036c0) already addresses this — I added err.response.text().catch(() => {}) in both catch blocks (_flush and _capture) to consume the error response body when PostHogFetchHttpError is thrown.

The approach of eagerly consuming the body at PostHogFetchHttpError construction time is cleaner long-term (single point of consumption), but would require changing the constructor signature and every call site that reads err.text/err.json. Kept this PR minimal and scoped to the reported issue — happy to refactor if maintainers prefer that approach.

@github-actions
Copy link
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, post a comment or remove the stale label – otherwise this will be closed in another week.

@github-actions github-actions bot added the stale label Mar 16, 2026
@jgarrison929
Copy link
Author

Still active — this fixes a real Cloudflare Workers deployment issue where unconsumed response bodies cause warnings/errors. Happy to address any maintainer feedback.

@github-actions github-actions bot removed the stale label Mar 17, 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.

_flush() never consumes fetch response body, causing Cloudflare Workers cross-request promise warnings

1 participant