## TL;DR

The fetch handler must return a Response (or a promise of one) on every path. A missing return in a branch, an async function that falls off the end, or returning a plain object instead of a Response crashes the request with an uncaught exception. Audit every branch for a return, wrap the handler so unexpected values become 500s, and the crash disappears.

## The query

```text
workerd uncaught exception: fetch handler did not return a Response
```

## Use this when

- The worker crashes on some requests but not others.
- The failing paths involve early returns, conditionals, or try/catch.
- The handler sometimes returns undefined according to type checks.

## Not for

- 'Response constructor: invalid redirect value' (constructor misuse, separate fix).
- Unhandled promise rejections elsewhere in the code.
- Wrong status codes or bodies (logic bug, not a crash).

## Steps

### Step 1: Find the path that returns nothing

Reproduce with the failing request and read the stack: it points at the handler's exit.

Expected result: one branch (an if without else, a catch that logs and falls through, a guard clause) has no return.

### Step 2: Make every branch return a Response

```js
export default {
  async fetch(request, env) {
    try {
      if (request.method !== "GET") {
        return new Response("method not allowed", { status: 405 });
      }
      return await handleGet(request, env);
    } catch (e) {
      console.error(e);
      return new Response("internal error", { status: 500 });
    }
  },
};
```

Expected result: no path falls through. The catch converts crashes into 500s.

### Step 3: Wrap helpers that build responses

If handleGet can return undefined, the bug moved, not vanished. Assert at the boundary or default it.

Expected result: helper contracts are explicit: they return Response, always.

### Step 4: Add a type check in CI

Type the handler as returning a promise of a Response and let the compiler flag undefined returns.

Expected result: the class of bug is caught at build time instead of in production.

### Step 5: Redeploy and exercise the failing path

Expected result: the previously crashing request returns a proper Response. Watch tail for the uncaught exception disappearing.

## Why it happens

JavaScript happily returns undefined from functions that fall off the end, and async handlers make the missing return invisible until runtime. The Workers runtime is strict: the contract is Response-or-crash. Conditional logic added over time creates the untested branch that finds the hole.

## Edge cases

- Returning a plain object is also a failure. It must be a Response instance.
- Middleware chains that forget to return the next handler produce this crash one layer removed from the bug.
- Scheduled and queue handlers have their own contracts; this error is specific to fetch.
- Awaiting a promise that resolves to undefined counts as returning undefined. Check what the awaited value is.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_9I0A6G22T8ShzoStt6DmXA
