denoland/deno · error · TypeError

Return value from onError handler must be a Response constru

Error message

Return value from onError handler must be a Response constructed via the Response constructor in this realm

What it means

The Response returned by onError goes through the same validation as a handler response: it must be a real Response carrying the internal slot of this realm. toInnerResponse returns undefined for cross-realm or polyfill instances (and prototype-forged objects), and serve rejects them with this TypeError. Because it is thrown inside the error path, only the internal 'Exception in onError while handling exception' log remains; the client gets a 500.

Source

Thrown at ext/http/00_serve.ts:761

        );
      }

      if (responseBodyUsed(response)) {
        throw new TypeError(
          "The body of the Response returned from the serve handler has already been consumed",
        );
      }
    } catch (error) {
      try {
        response = await onError(error);
        if (!ObjectPrototypeIsPrototypeOf(ResponsePrototype, response)) {
          throw new TypeError(
            "Return value from onError handler must be a response or a promise resolving to a response",
          );
        }
        inner = toInnerResponse(response);
        if (inner === undefined) {
          throw new TypeError(
            "Return value from onError handler must be a Response constructed via the Response constructor in this realm",
          );
        }
      } catch (error) {
        if (otelState.METRICS_ENABLED) {
          op_http_metric_handle_otel_error(req);
        }
        internals.log(
          "error",
          "Exception in onError while handling exception",
          error,
        );
        response = internalServerError();
        inner = toInnerResponse(response);
      }
    }

    if (span) {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Re-create the response inside onError with the built-in Response constructor
  2. Buffer cross-realm bodies first: new Response(await foreign.text(), { status: 500, headers: foreign.headers })
  3. Ensure any Response subclass used in onError calls super(...)

Example fix

// before
import { Response as NodeResponse } from "npm:undici";
Deno.serve({
  onError: (e) => new NodeResponse(e.message, { status: 500 }), // wrong realm
  handler,
});

// after
import { Response as NodeResponse } from "npm:undici";
Deno.serve({
  onError: (e) => new Response(e.message, { status: 500 }), // built-in Response
  handler,
});
Defensive patterns

Strategy: type-guard

Validate before calling

// Normalize onError output to this realm's Response
const onError = (err) => {
  const r = buildErrorResponse(err); // may be cross-realm
  return r instanceof Response && r.constructor === Response
    ? r
    : new Response("Internal Server Error", { status: 500 });
};

Type guard

function isRealmResponse(v) {
  return v instanceof Response && v.constructor === Response;
}

Try / catch

// A failing onError already degrades to a logged 500; keep it simple:
const onError = (err) => new Response("Internal Server Error", { status: 500 });

Prevention

When it happens

Trigger: onError returning a Response imported from an npm package (e.g. undici), a Response built inside a Worker, or a subclass that skipped super().

Common situations: Centralized error handlers that re-export or forward Response objects from third-party SDKs; polyfilled environments layered on top of Deno.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/e73e78f6d6c228f9. Report an issue: GitHub.