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

  1. Make every code path in fetch return a Response (add a final 'return new Response(...)' as the default)
  2. Return undefined only on the WebSocket upgrade path (export websocket handlers and let the runtime own the upgrade)
  3. Add an exported error handler as a safety net so failures render a controlled page instead of a bare 500
  4. 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

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.