denoland/deno · error · TypeError

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

Error message

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

What it means

When the main handler throws (including on the validation errors above), Deno.serve calls the configured onError and applies the same contract: it must return a Response. This TypeError fires when onError's settled return value is not on the Response prototype chain — typically because a logging-only onError implicitly returns undefined. The failure then falls back to a built-in 500 and the error is logged as 'Exception in onError while handling exception'.

Solutions

  1. End every onError path with `return new Response('Internal Server Error', { status: 500 })`
  2. If you only want logging, keep the default onError and do logging in the handler's catch instead
  3. For typed errors, map to statuses: `return new Response(e.message, { status: e.status ?? 500 })`

Example fix

// before
Deno.serve({
  onError: (e) => { console.error('failed:', e); }, // returns undefined
  handler,
});

// after
Deno.serve({
  onError: (e) => {
    console.error('failed:', e);
    return new Response('Internal Server Error', { status: 500 });
  },
  handler,
});
Defensive patterns

Strategy: type-guard

Validate before calling

// onError must produce a Response on every path:
const onError = (e: unknown): Response => {
  console.error(e);
  return e instanceof HttpError
    ? new Response(e.message, { status: e.status })
    : new Response('Internal Server Error', { status: 500 });
};

Type guard

const isResponse = (v: unknown): v is Response => v instanceof Response;

Try / catch

// If onError itself is third-party, wrap it so its result is always a Response:
Deno.serve({
  handler,
  onError: (e) => {
    const out = foreignOnError(e);
    return out instanceof Response ? out : new Response('Internal Server Error', { status: 500 });
  },
});

Prevention

When it happens

Trigger: `onError: (e) => { console.error(e); }` (returns undefined), an async onError that awaits logging but forgets to return, onError returning a plain object or string, or onError throwing and its replacement value being validated.

Common situations: Adding an onError purely for structured logging/metrics and forgetting it must also produce the response, teams assuming onError behaves like Express error middleware (which sends the response itself).

Related errors


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

Appendix: source

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

        );
      }

      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 abd22074e4)