oven-sh/bun · error · Error
fetch() did not return a Response
Error message
fetch() did not return a Response
What it means
The bun-lambda runtime invokes your exported fetch(request) handler and requires the resolved value to be a genuine Response instance; undefined is only tolerated when a WebSocket upgrade was performed (the runtime substitutes its stored upgrade response). Anything else — undefined with no upgrade, null, a plain object, or a Response-like from a duplicate Response class — throws. The runtime catches it, logs via console.error, optionally calls your exported error(cause) handler, and returns a 500 Response.
Source
Thrown at packages/bun-lambda/runtime.ts:613
? options.port
: typeof options.port === "string"
? parseInt(options.port)
: this.port;
this.hostname = options.hostname ?? this.hostname;
this.development = options.development ?? this.development;
}
async fetch(request: Request): Promise<Response> {
this.pendingRequests++;
try {
let response = await this.#lambda.fetch(request, this);
if (response instanceof Response) {
return response;
}
if (response === undefined && this.#upgrade !== null) {
return this.#upgrade;
}
throw new Error("fetch() did not return a Response");
} catch (cause) {
console.error(cause);
if (this.#lambda.error !== undefined) {
try {
return await this.#lambda.error(cause);
} catch (cause) {
console.error(cause);
}
}
return new Response(null, { status: 500 });
} finally {
this.pendingRequests--;
this.#upgrade = null;
}
}
upgrade<T = undefined>(
request: Request,View on GitHub (pinned to 8c5296ac45)
Solutions
- Make every code path in fetch return a Response (add a final 'return new Response(...)' as the default)
- Return undefined only on the WebSocket upgrade path (export websocket handlers and let the runtime own the upgrade)
- Add an exported error handler as a safety net so failures render a controlled page instead of a bare 500
- Ensure a single copy of the runtime/types is bundled so instanceof Response holds
Example fix
// before
export default {
async fetch(request) {
if (request.method === 'GET') return new Response('hi');
// falls through -> "fetch() did not return a Response" + 500
},
};
// after
export default {
async fetch(request) {
if (request.method === 'GET') return new Response('hi');
return new Response('Method Not Allowed', { status: 405 });
},
}; Defensive patterns
Strategy: type-guard
Validate before calling
const isResponse = (v: unknown): v is Response => v instanceof Response;
const raw = await handler.fetch(request);
if (!isResponse(raw)) throw new TypeError('handler must return a Response'); Type guard
const isResponse = (v: unknown): v is Response => v instanceof Response || (!!v && typeof (v as Response).body !== 'undefined' && typeof (v as Response).status === 'number');
Try / catch
try {
return await lambda.fetch(request);
} catch (cause) {
console.error(cause);
return lambda.error ? await lambda.error(cause) : new Response(null, { status: 500 });
} Prevention
- End every fetch handler with a default 'return new Response(...)'
- Export an error handler so unexpected failures render a controlled response
- Return undefined only on the WebSocket upgrade path
- Avoid bundling duplicate runtime copies that break instanceof Response
When it happens
Trigger: A handler branch that falls through without returning; returning a serialized or plain-object response; returning undefined on a non-WebSocket route; instanceof Response failing because the handler constructs Responses from a second bundled copy of the runtime types.
Common situations: Porting Bun.serve/Cloudflare-style workers to bun-lambda and forgetting the default return; early-return refactors that drop the Response; bundlers that duplicate polyfills making cross-realm Response objects fail instanceof.
Related errors
AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16).
Data as JSON: /api/errors/adad9e1b4cb5607a.
Report an issue: GitHub.