{"record":{"id":"04bde810e2a261ae","repo":"denoland/deno","slug":"return-value-from-onerror-handler-must-be-a-respon","errorCode":null,"errorMessage":"Return value from onError handler must be a response or a promise resolving to a response","messagePattern":"Return value from onError handler must be a response or a promise resolving to a response","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/http/00_serve.ts","lineNumber":774,"sourceCode":"        );\n      }\n\n      if (inner.type === \"error\") {\n        throw new TypeError(\n          \"Return value from serve handler must not be an error response (like Response.error())\",\n        );\n      }\n\n      if (responseBodyUsed(response)) {\n        throw new TypeError(\n          \"The body of the Response returned from the serve handler has already been consumed\",\n        );\n      }\n    } catch (error) {\n      try {\n        response = await onError(error);\n        if (!ObjectPrototypeIsPrototypeOf(ResponsePrototype, response)) {\n          throw new TypeError(\n            \"Return value from onError handler must be a response or a promise resolving to a response\",\n          );\n        }\n        inner = toInnerResponse(response);\n        if (inner === undefined) {\n          throw new TypeError(\n            \"Return value from onError handler must be a Response constructed via the Response constructor in this realm\",\n          );\n        }\n      } catch (error) {\n        if (otelState.METRICS_ENABLED) {\n          op_http_metric_handle_otel_error(req);\n        }\n        internals.log(\n          \"error\",\n          \"Exception in onError while handling exception\",\n          error,\n        );","sourceCodeStart":756,"sourceCodeEnd":792,"githubUrl":"https://github.com/denoland/deno/blob/abd22074e47c6a5cd14e9e4e84743f084aa5a575/ext/http/00_serve.ts#L756-L792","documentation":"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'.","triggerScenarios":"`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.","commonSituations":"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).","solutions":["End every onError path with `return new Response('Internal Server Error', { status: 500 })`","If you only want logging, keep the default onError and do logging in the handler's catch instead","For typed errors, map to statuses: `return new Response(e.message, { status: e.status ?? 500 })`"],"exampleFix":"// before\nDeno.serve({\n  onError: (e) => { console.error('failed:', e); }, // returns undefined\n  handler,\n});\n\n// after\nDeno.serve({\n  onError: (e) => {\n    console.error('failed:', e);\n    return new Response('Internal Server Error', { status: 500 });\n  },\n  handler,\n});","handlingStrategy":"type-guard","validationCode":"// onError must produce a Response on every path:\nconst onError = (e: unknown): Response => {\n  console.error(e);\n  return e instanceof HttpError\n    ? new Response(e.message, { status: e.status })\n    : new Response('Internal Server Error', { status: 500 });\n};","typeGuard":"const isResponse = (v: unknown): v is Response => v instanceof Response;","tryCatchPattern":"// If onError itself is third-party, wrap it so its result is always a Response:\nDeno.serve({\n  handler,\n  onError: (e) => {\n    const out = foreignOnError(e);\n    return out instanceof Response ? out : new Response('Internal Server Error', { status: 500 });\n  },\n});","preventionTips":["Annotate onError with an explicit Response return type so TS flags missing returns","Remember Deno.serve onError must RETURN the response, unlike Express error middleware","If you only want logging, keep the default onError and log in the handler instead"],"tags":["http","deno-serve","on-error","handler-contract","validation"],"backgroundTag":"serve-handler-invalid-return","analyzedSha":"abd22074e47c6a5cd14e9e4e84743f084aa5a575","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}