microsoft/playwright · error · Error

Response body is unavailable

Error message

Response body is unavailable

What it means

Thrown by createResponseBodyCallback (crNetworkManager.ts:595) when InterceptableRequest.from(request) returns undefined — the request has been detached from its CDP session (the symbol was cleared by detachIfNeeded). The response body can no longer be fetched via Network.getResponseBody because there is no session/route binding left.

Source

Thrown at packages/playwright-core/src/server/chromium/crNetworkManager.ts:595

  readonly _originalRequestRoute: RouteImpl | undefined;
  session: CRSession;

  private static from(request: network.Request): InterceptableRequest | undefined {
    return (request as any)[kInterceptableRequest];
  }

  static detachIfNeeded(request: network.Request, session: CRSession) {
    if (InterceptableRequest.from(request)?.session === session)
      (request as any)[kInterceptableRequest] = undefined;
  }

  static createResponseBodyCallback(request: network.Request): () => Promise<Buffer> {
    return async () => {
      // Lookup the request lazily, so that the response does not retain the
      // InterceptableRequest and its session after detachIfNeeded().
      const interceptable = InterceptableRequest.from(request);
      if (!interceptable)
        throw new Error('Response body is unavailable');
      const contentLength = request._existingResponse()?.headerValue('content-length');
      const expectedLength = contentLength ? +contentLength : undefined;

      const session = interceptable.session;
      const response = await session.send('Network.getResponseBody', { requestId: interceptable._requestId });
      if (response.body || !expectedLength)
        return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');

      // Make sure no network requests sent while reading the body for fulfilled requests.
      if (interceptable._originalRequestRoute?._fulfilled)
        return Buffer.from('');

      // Re-fetching the resource may produce side effects on the server, only
      // do it for GETs of static subresources and prefetch requests.
      if (request.method() !== 'GET')
        return Buffer.from('');
      if (!kRefetchSafeResourceTypes.has(request.resourceType())) {
        const rawHeaders = await request.internalRawRequestHeaders();

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Read response.body() before fulfilling or continuing the route.
  2. Cache the body bytes in the route handler if you need them later.
  3. Detach-aware: only read the body while the page that produced the response is still open.

Example fix

// before
route.fulfill({ status: 200 });
const body = await response.body();
// after
const body = await response.body();
await route.fulfill({ status: 200, body });
Defensive patterns

Strategy: validation

Validate before calling

// Read the body BEFORE fulfilling or continuing the route.
const response = await route.fetch();
const body = await response.body();
await route.fulfill({ status: response.status(), body, headers: response.headers() });

Prevention

When it happens

Trigger: Calling response.body() after route.fulfill() or route.continue() detached the request; reading the body after the page/context that owned the request was closed; accessing the body of a request whose route was already resolved.

Common situations: A request handler that fulfills a route and then logs response.body(); page.on('response') listeners that outlive the page; retries that re-read bodies after fulfill.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/a4687de1de0bda58. Report an issue: GitHub.