# Cloudflare Workers error 1101: causes and how to fix it

> Error 1101 means your Cloudflare Worker threw an uncaught JavaScript exception. Find the exception in logs, match it to its cause, fix it and roll back fast.

Part 7 of [Platform playbooks](https://polylane.com/series/platform-playbooks/)
By Boris Tane, Founder of Polylane · Published September 30, 2026 · 13 min read
Canonical: https://polylane.com/learn/troubleshooting/cloudflare-workers-error-1101/

Error 1101 means your Worker threw a JavaScript exception it never caught, so it returned no response and Cloudflare served an error page. To find the exception message, run wrangler tail with the error status filter, or filter Workers Logs on the exception outcome. The message tells you the cause: an unresolved promise, a lost this reference, an I/O object shared between requests, or an ordinary bug in your code. If users are affected, roll back with wrangler rollback first, then fix the code and redeploy.

Error 1101 means your Worker threw a JavaScript exception and never caught it. The Workers runtime had no response to send, so it served its own error page. Cloudflare's support docs call this a rendering error. The cause is in your Worker code, or in a dependency your code calls.

Every fix starts in the same place: read the exception message in Workers Logs or `wrangler tail`. That message tells you which cause you have, from a promise that never settles to a method that lost its `this`. If users are hitting the error right now, roll back first. Then fix the code, reproduce the error locally to confirm the fix, and redeploy.

## What error 1101 means for your Worker

When a production Worker hits an error that stops it returning a response, the client gets a Cloudflare error page with a code. Code 1101 means the Worker threw a JavaScript exception. The nearby codes each mean something different, so check which one you have before you start debugging:

- **1101**: the Worker threw a JavaScript exception.
- **1102**: the Worker exceeded its CPU time limit. The fix here is to do less work per request. It has nothing to do with exception handling.
- **1019**: the Worker hit the loop limit. A Worker cannot call itself or another Worker more than 16 times.
- **1027**: the Worker exceeded the free tier daily request limit.
- **1021, 1022, 1024 and 1042**: routing and subrequest problems, such as requesting a host the Worker cannot reach.
- **Other 11xx codes**: these usually point at a problem in the Workers runtime itself. Check the Cloudflare status page before you touch your code.

A Workers exception can also show up as a plain HTTP 500, so a 500 in front of a Worker deserves the same checks. The error page carries a Ray ID. Keep it: Cloudflare Support asks for the Ray ID, the Worker name, recent code changes and steps to reproduce.

Some failures never produce an error page. Cloudflare lists `Network connection lost`, memory limit errors while reading a stream, and `daemonDown` as runtime errors. The user doesn't see them, and you only find them in logs.

## Find the exception behind your 1101

I built and led the Workers observability team at Cloudflare, and on a 1101 I look for the exception message before anything else. Every later step depends on it.

### Check the Worker is writing logs

New Workers have observability turned on by default. Older Workers may not. Open your Wrangler config and make sure it contains this (Workers Logs needs Wrangler 3.78.6 or later):

```jsonc
{
  "observability": {
    "enabled": true,
    "head_sampling_rate": 1
  },
  "upload_source_maps": true
}
```

If you had to add it, redeploy. `head_sampling_rate` sets the share of requests that get logged, from 0 to 1. The default is 1, which logs every request. If someone set it to 0.01 to save cost, 99 out of every 100 failing requests leave no log, and a rare 1101 can go unrecorded.

### Filter Workers Logs down to the failing requests

1. In the Cloudflare dashboard, go to **Workers & Pages**.
2. Select your Worker, then **Observability**.
3. Filter on `$workers.outcome = "exception"` to list every request that ended in an uncaught exception.
4. For a wider net, filter on `$metadata.error EXISTS` to see every log line that has an error attached.
5. Open one event and read the exception name, message and stack.

For a quick check on scale, the Worker's metrics page has an **Errors by invocation status** chart. The **Uncaught Exception** series counts invocations that ended in an uncaught exception, which is the failure behind a 1101. **Exceeded CPU Time Limits** and **Exceeded Memory** are counted separately.

### Tail the Worker live while you reproduce the error

`wrangler tail` streams invocation logs, your own `console.log` lines and uncaught exceptions as they happen. Filter it to errors, or on a busy Worker the stream fills with successful requests:

```bash
npx wrangler tail my-worker --status error --format pretty

# JSON output: exceptions are in the exceptions field
npx wrangler tail my-worker --status error | jq '.exceptions'

# Narrow to requests from your own machine while you reproduce
npx wrangler tail my-worker --status error --ip self
```

Each tail entry is a JSON object with an `outcome`, an `exceptions` array, a `logs` array and the request details under `event.request`. Once you've shipped a fix, keep the tail running to confirm the exceptions have stopped.

### Turn on source maps so the stack trace points at your code

Most Workers are bundled and minified before deploy. Without source maps, a stack trace points at one minified JavaScript file, with wrong line numbers and no TypeScript file names. Set `upload_source_maps` to `true`, as in the config above, and Wrangler generates and uploads source maps on every `wrangler deploy` or `wrangler versions deploy`. This needs Wrangler 3.46.0 or later, and each source map can be up to 15 MB gzipped.

Cloudflare applies the source map after the invocation ends, and you see the mapped trace in real-time logs and Tail Workers. Logging `err.stack` from inside your Worker still prints the minified trace, because source maps aren't available inside the Worker at runtime.

### Line up the first failures with a deploy

Most 1101 spikes start at a deploy. List recent versions and deployments, then tail a single version:

```bash
npx wrangler deployments list
npx wrangler versions list
npx wrangler tail my-worker --status error --version-id <VERSION_ID>
```

If you use gradual deployments, traffic is split between two versions. Filtering by version ID shows whether only the new version throws.

## Match the exception message to its cause

### `The script will never generate a response`

The runtime found that all the code for the request had finished, with nothing left in the event loop, and yet no Response had been returned. Cloudflare names two causes.

**A promise that never resolves or rejects.** Somewhere on the path to your Response, a promise waits on something that never happens. In Node or a browser this code would hang forever. Workers throws an error so you can find it. Make sure every path settles the promise:

```ts
// Hangs if "ready" never fires: nothing settles the promise
function waitForReady(emitter) {
  return new Promise((resolve) => {
    emitter.on("ready", resolve);
  });
}

// Every path settles
function waitForReady(emitter, ms = 5000) {
  return new Promise((resolve, reject) => {
    emitter.on("ready", resolve);
    setTimeout(() => reject(new Error("ready timed out")), ms);
  });
}
```

Check your dependencies too, since the promise that never settles is often inside a library. The typescript-eslint rule `no-floating-promises` reports promises that are created and never handled.

**A WebSocket whose server side is never closed.** If the `close` handler never calls `server.close()`, the connection stays open and the runtime throws this error. With the `web_socket_auto_reply_to_close` compatibility flag, the runtime completes the close handshake for you. The flag is on by default for compatibility dates on or after `2026-04-07`, so moving your compatibility date forward makes this cause much less likely.

### `TypeError: Illegal invocation`

The full message says a function was called with an incorrect `this` reference. It usually comes from destructuring a runtime object whose methods depend on `this`, and `ctx` is the usual one:

```ts
// Throws: waitUntil has lost its this
const { waitUntil } = ctx;
waitUntil(promise);

// Works
ctx.waitUntil(promise);

// Also works, if you need a standalone reference
const waitUntilBound = ctx.waitUntil.bind(ctx);
```

Thrown on the request path and left uncaught, it becomes a 1101.

### `Cannot perform I/O on behalf of a different request`

Streams, request bodies and responses belong to the request that created them. This error means one request tried to use an I/O object that another request created. The usual cause is caching a `Response` or `Request` in global scope. It needs a second request to show up, so a single test request won't trigger it. Send two requests in quick succession in local development and you can reproduce it. Keep the data in global scope and build a new Response each time:

```ts
let cachedBody = null;

export default {
  async fetch(request, env, ctx) {
    if (cachedBody) return new Response(cachedBody);
    const upstream = await fetch("https://api.example.com/config");
    cachedBody = await upstream.text();
    return new Response(cachedBody);
  },
};
```

If you need state shared across requests, use Durable Objects. For a cache shared across requests, use Workers KV.

### A plain TypeError, ReferenceError or rejected promise

Cloudflare's support page lists the everyday causes: undefined variables or functions, type errors, promise rejections and failed network requests. Some common forms:

- Reading a property of `undefined`, often an `env` binding or secret that exists in one environment and is missing in another.
- Calling `await res.json()` on an upstream that returned an HTML error page, which throws a `SyntaxError`.
- A rejected `fetch` or binding call with no `try...catch` around it.

The stack trace, once it's source mapped, gives you the file and line. Compare the failing line with the diff of the most recent deploy.

### `Network connection lost` from a fetch or binding

This is a connection failure. Cloudflare's advice is to catch the `fetch` or binding call and retry it. Put a small retry with a limit around calls to upstreams that sometimes drop connections.

## Stop the errors now, then fix the code

### Roll back to the last good version

A rollback immediately creates a new deployment from the version you pick, and that deployment serves traffic on all your routes and domains:

```bash
# Roll back to the version uploaded before the latest one
npx wrangler rollback

# Roll back to a specific version, skipping the prompts
npx wrangler rollback <VERSION_ID> --message "Roll back: 1101 on /checkout"
```

In the dashboard, go to your Worker, then **Deployments**. Select the three-dot menu on the version you want and choose **Rollback**. If a split deployment is live, rolling back replaces both versions with the one you chose, at 100% of traffic.

Before you roll back, know these limits:

- You can only roll back to the 100 most recently published versions.
- A rollback does not change bindings or the resources they point to. If the data shape changed between versions, the old code can throw against the new data.
- A rollback is blocked if a Durable Object class migration happened in between, or if the target version binds to an R2 bucket, KV namespace or queue that no longer exists.

### Fall back to your origin when the Worker sits in front of one

If your Worker proxies to an origin, call `ctx.passThroughOnException()` at the top of the handler. When your code throws an unhandled exception, the request then goes to the origin as if the Worker weren't there. It doesn't cover errors from the origin `fetch()` itself, so wrap that call in `try...catch`. It also can't replay a request body the failed attempt already read.

```ts
export default {
  async fetch(request, env, ctx) {
    ctx.passThroughOnException();
    return fetch(request);
  },
};
```

### Reproduce the exception locally before redeploying

`wrangler dev` runs your Worker on `localhost:8787`, and `console.log` output and exceptions print in the terminal. Send the request that failed in production:

```bash
npx wrangler dev
curl -i http://localhost:8787/checkout

# If the bug depends on real data in KV, D1 or R2
npx wrangler dev --remote
```

Once you can reproduce it, write a test that fails for the same reason, fix the code, and watch the test pass before you deploy.

## Catch exceptions at the top of the handler

A top-level `try...catch` turns an unexpected exception into a response you control, and logs the details as structured fields you can filter on:

```ts
export default {
  async fetch(request, env, ctx) {
    try {
      return await handle(request, env, ctx);
    } catch (err) {
      console.log({
        level: "error",
        message: "unhandled exception",
        error: String(err),
        stack: err instanceof Error ? err.stack : undefined,
        path: new URL(request.url).pathname,
      });
      return new Response("Something went wrong", { status: 500 });
    }
  },
};
```

Three details make this work:

- **Write `return await`.** A bare `return handle(...)` hands back the promise without awaiting it, so if it rejects, the rejection happens outside the current function. The `catch` misses it and the request still ends as a 1101. Awaiting the promise throws the exception into the current function, where the `catch` can handle it.
- **Log objects.** Workers Logs pulls out and indexes the fields of a JSON log line, so you can filter on `level` or `path` directly, without a text search.
- **Watch the outcome change.** A caught exception no longer counts as an exception outcome, so the `$workers.outcome = "exception"` filter stops finding it. Filter on your own `level` field to find these errors.

If you also send errors to an outside service, pass the request to `ctx.waitUntil()`. Otherwise it can be cancelled when the invocation ends.

## What goes wrong while chasing a 1101

- **The logs are sampled or turned off.** A low `head_sampling_rate` drops most failing requests. Fix: set it to 1 while you investigate, or keep sampling and rely on `wrangler tail --status error` during the incident.
- **The tail enters sampling mode.** On a busy Worker, real-time logs drop messages and print a warning. Fix: add filters such as `--status error` or `--ip self`. It can take up to a minute to leave sampling mode after you add a filter.
- **Teammates can't open the tail.** At most 10 clients can view a Worker's logs at once, counting dashboard sessions and `wrangler tail` together. Fix: close idle sessions during an incident.
- **The stack trace is minified.** Fix: set `upload_source_maps` and redeploy.
- **The evidence ages out.** Workers Logs keeps logs for 3 days on the Free plan and 7 days on the Paid plan, and real-time logs store nothing. Fix: copy the exception and Ray ID into the incident notes, and export logs through OpenTelemetry, Logpush or a Tail Worker if you need them for longer.
- **You debug a 1102 as if it were a 1101.** A CPU limit is a different failure, and its fix is to do less work per request, so exception handling won't solve it. Fix: read the code on the error page and the **Errors by invocation status** chart before you start.
- **The rollback throws too.** The older code meets data or bindings that have changed since. Fix: check what changed between the two versions before you roll back.

## Keeping 1101s out of production

- Observability is on, with `head_sampling_rate` at a level that still catches rare errors.
- `upload_source_maps` is set, so every exception maps back to a real file and line.
- The `no-floating-promises` lint rule runs in CI.
- The fetch handler has a top-level `try...catch` with `return await` and structured error logs.
- Calls to upstreams and bindings have a timeout, and a retry where it's safe to retry.
- Nothing request-scoped (Request, Response, streams) lives in global scope.
- The compatibility date is recent enough to pick up runtime fixes such as automatic WebSocket close.
- Risky changes go out as gradual deployments, and you watch errors per version with `--version-id`.
- Exception logs flow to the tool that pages your team, so a spike reaches you before users report it.

## Watching Workers for uncaught exceptions with Polylane

Polylane connects to your Cloudflare account with a read-only token and triages the account's alerts automatically. A Worker that starts throwing becomes one issue, which an agent investigates using the logs and your code. When the cause is a code change, the fix arrives as a pull request for you to review. It also raises an advisory on any Worker with logs disabled, the gap that leaves a 1101 with nothing to explain it.

Running on Cloudflare? See [how Polylane monitors Cloudflare in production](https://polylane.com/for/cloudflare/).

## Common questions

**Is error 1101 caused by Cloudflare or by my code?**

Almost always by your code. Cloudflare defines 1101 as the Worker throwing a JavaScript exception, so the exception came from your code or a library it calls. Other 11xx codes usually mean a problem in the Workers runtime itself, and for those you should check the Cloudflare status page first.

**What is the difference between error 1101 and error 1102?**

1101 means the Worker threw an exception it didn't catch. 1102 means the Worker went over its CPU time limit. A 1102 has nothing to do with exception handling, and the fix is to cut the work done per request. The Errors by invocation status chart counts them separately, as Uncaught Exception and Exceeded CPU Time Limits.

**Why can't I find the exception in my Worker logs?**

Check that observability is enabled in your Wrangler config, and that head_sampling_rate isn't set so low that it drops most requests. Workers Logs keeps logs for 3 days on the Free plan and 7 days on the Paid plan, and real-time logs store nothing at all. Run npx wrangler tail with --status error while you reproduce the request to see the exception live.

**Why does my 1101 stack trace point at minified code?**

Your source maps aren't uploaded. Add upload_source_maps set to true in your Wrangler config (Wrangler 3.46.0 or later) and redeploy. Cloudflare then maps stack traces back to your original files in real-time logs and Tail Workers. A single source map can be up to 15 MB gzipped.

**How do I roll back a Worker that started throwing 1101 errors?**

Run npx wrangler rollback to go back to the version uploaded before the latest one, or pass a version ID from npx wrangler versions list. You can also use the Rollback option on the Deployments tab in the dashboard. You can roll back to any of the 100 most recent versions, but bindings aren't changed, and a Durable Object migration or a deleted R2 bucket, KV namespace or queue blocks the rollback.

**What does The script will never generate a response mean?**

It's a 1101 that the runtime raises when all your code has finished and the event loop is empty, but no Response was returned. Cloudflare names two causes: a promise that never resolves or rejects, and a WebSocket whose server side is never closed. Make sure every path settles its promise, and turn on the no-floating-promises lint rule to catch unhandled promises.

**Can I replace the 1101 error page with my own response?**

Yes. Wrap your handler in try...catch, use return await so a rejected promise throws inside the function where the catch can handle it, and return your own 500 response. If the Worker sits in front of an origin, ctx.passThroughOnException() sends the request to the origin whenever your code throws an unhandled exception.

## Sources

- [Errors and exceptions (Cloudflare Workers docs)](https://developers.cloudflare.com/workers/observability/errors/)
- [Error 1101 (Cloudflare Support docs)](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/error-1101/)
- [Workers Logs (Cloudflare Workers docs)](https://developers.cloudflare.com/workers/observability/logs/workers-logs)
- [Real-time logs (Cloudflare Workers docs)](https://developers.cloudflare.com/workers/observability/logs/real-time-logs/)
- [Source maps and stack traces (Cloudflare Workers docs)](https://developers.cloudflare.com/workers/observability/source-maps/)
- [Wrangler Workers commands (Cloudflare Workers docs)](https://developers.cloudflare.com/workers/wrangler/commands/workers/)
- [Rollbacks (Cloudflare Workers docs)](https://developers.cloudflare.com/workers/configuration/versions-and-deployments/rollbacks/)
- [await (MDN Web Docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)
- [Polylane documentation](https://docs.polylane.com/llms-full.txt)
- [Polylane Cloudflare integration](https://docs.polylane.com/integrations/cloudflare)

## About the author

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

## More in this series

[Platform playbooks](https://polylane.com/series/platform-playbooks/)

1. [How to Monitor a Django App on Render](https://polylane.com/learn/monitoring-coverage/how-to-monitor-a-django-app-on-render/)
2. [How to Monitor a FastAPI App on Railway: Logs, Traces and Alerts](https://polylane.com/learn/monitoring-coverage/how-to-monitor-a-fastapi-app-on-railway/)
3. [How to Monitor a Supabase App in Production](https://polylane.com/learn/monitoring-coverage/how-to-monitor-a-supabase-app-in-production/)
4. [How to Debug Cloudflare Workers Errors: Logs, Traces and Error Codes](https://polylane.com/learn/troubleshooting/how-to-debug-cloudflare-workers-errors/)
5. [How to debug Vercel function timeouts](https://polylane.com/learn/troubleshooting/how-to-debug-vercel-function-timeouts/)
6. [Vercel 504 Gateway Timeout on Serverless Functions: Causes and Fixes](https://polylane.com/learn/troubleshooting/vercel-504-gateway-timeout-on-serverless-functions/)
7. [Cloudflare Workers error 1101: causes and how to fix it](https://polylane.com/learn/troubleshooting/cloudflare-workers-error-1101/)
8. [Cloudflare Hyperdrive connection errors: causes and fixes](https://polylane.com/learn/troubleshooting/cloudflare-hyperdrive-connection-errors/)
9. [How to Monitor a Convex App in Production](https://polylane.com/learn/monitoring-coverage/how-to-monitor-a-convex-app-in-production/)

Previous: [Vercel 504 Gateway Timeout on Serverless Functions: Causes and Fixes](https://polylane.com/learn/troubleshooting/vercel-504-gateway-timeout-on-serverless-functions/)
Next: [Cloudflare Hyperdrive connection errors: causes and fixes](https://polylane.com/learn/troubleshooting/cloudflare-hyperdrive-connection-errors/)

## Related

- [How to Use Claude Code to Debug Production Issues](https://polylane.com/learn/ai-in-production/how-to-use-claude-code-to-debug-production-issues/): Debug production issues with Claude Code: get logs and traces into the session, test hypotheses against evidence, stay read-only and ship a verified fix.
- [How to keep AI coding agents from breaking production](https://polylane.com/learn/ai-in-production/how-to-keep-ai-coding-agents-from-breaking-production/): Stop AI coding agents breaking production: scoped credentials, branch protection they can't bypass, required checks that block, and production-aware review.
- [Fix “Durable Object reset because its code was updated”](https://polylane.com/learn/troubleshooting/cloudflare-durable-object-reset-because-its-code-was-updated/): Why a deploy makes Cloudflare Durable Objects throw “reset because its code was updated”, and how to retry safely, keep clients connected and lose no state.
- [Fix Cloudflare “Error 1102: Worker exceeded resource limits”](https://polylane.com/learn/troubleshooting/cloudflare-error-1102-worker-exceeded-resource-limits/): Why a Cloudflare Worker returns Error 1102, how to tell a CPU time overrun from a 128 MB memory overrun, and the code and Wrangler changes that fix each.

Get started with one command: `curl -fsSL https://polylane.com/setup | bash` installs the CLI, connects your coding agents, and creates the account.
