denoland/deno · error · TypeError

Return value from onError handler must be a response or a pr

Error message

Return value from onError handler must be a response or a promise resolving to a response

What it means

When a serve handler throws, Deno calls the configured onError callback and expects it (or its promise) to resolve to a Response to send instead. If onError returns a non-Response, serve throws this TypeError; the fallback then logs 'Exception in onError while handling exception' and closes the request with a 500.

Source

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

        );
      }

      if (inner.type === "error") {
        throw new TypeError(
          "Return value from serve handler must not be an error response (like Response.error())",
        );
      }

      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,
        );

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Make onError return a Response: new Response(message, { status: 500 })
  2. Cover every branch of onError, including its own catch/finally paths
  3. Keep using throw inside handlers; reserve onError for shaping the error page

Example fix

// before
Deno.serve({
  onError: (e) => `Error: ${e.message}`, // string, not a Response
  handler,
});

// after
Deno.serve({
  onError: (e) => new Response(`Error: ${e.message}`, { status: 500 }),
  handler,
});
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure onError always yields a Response
const safeOnError = async (err) => {
  const r = await onError?.(err);
  return r instanceof Response ? r : new Response("Internal Server Error", { status: 500 });
};

Type guard

function isValidOnErrorResult(v) {
  return v instanceof Response;
}

Try / catch

// serve already wraps a failing onError with a 500 and logs
// 'Exception in onError while handling exception'; keep onError trivial and total:
const onError = (err) => new Response(`Error: ${err.message}`, { status: 500 });

Prevention

When it happens

Trigger: onError returning a string like `Error: ${message}`; onError that forgets a return on some branch; onError returning the caught error object itself; onError that returns undefined after logging.

Common situations: Converting an Express-style error middleware that replied with res.send(text); adding logging-first onError handlers without a return value.

Related errors


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