workerd error: fetch event handler crashed after returning response
Fixes Workers that crash after the response is returned because of post-response async work. This skill shows how to move background tasks into ctx.waitUntil so the isolate stays alive until they settle. Use it when workerd logs show the fetch handler crashing after the response went out, typically analytics or logging writes done after return.
Fix fetch event handler crashed after returning response
TL;DR
Move all async work that happens after the response into ctx.waitUntil(). In the Workers runtime, anything you await after returning the response gets killed when the isolate suspends, and the resulting rejection surfaces as a crash after the response. Wrap the background work in ctx.waitUntil(promise) and make that promise catch its own errors.
Verbatim error
workerd error: fetch event handler crashed after returning responseSteps
- Find the awaits that run after the
returnin your fetch handler (analytics writes, logging calls, fire-and-forget fetches). Expected: you locate the post-response async work. - Change the handler signature to accept the context:
async fetch(request, env, ctx). Expected:ctxis available in the handler. - Replace bare post-response awaits with
ctx.waitUntil(doWork()), wheredoWork()is an async function holding the background task. Expected: the response still returns fast while the work continues. - Make
doWork()catch its own errors internally (waitUntil rejections are logged, not fatal, but unhandled ones are noisy). Expected: no unhandled promise rejections in tail. - Load-test the endpoint and watch
wrangler tail. Expected: response times stay low and no crash logs appear.
Use this when
- workerd logs show "fetch event handler crashed after returning response"
- Analytics, logging, or cache writes happen after the response is returned
- Fire-and-forget fetches after
returnfail intermittently
Not for this skill when
- The crash happens before the response is returned (debug the handler itself)
- You are streaming a response body (different lifetime rules apply)
- The code runs in a Durable Object (use
state.waitUntilthere)
Variant phrasings
- worker crashed after respond
- waitUntil not working cloudflare workers
- background task killed after response returned
- promise rejected after response sent workers
Why it happens
After the response is returned, the runtime may suspend the isolate at any moment. Promises that are not attached to waitUntil get cancelled, and their rejections surface as crashes attributed to the fetch handler. ctx.waitUntil() extends the isolate lifetime until the promise settles, which is the supported way to do post-response work.
Edge cases
waitUntilgives you up to 30 seconds of post-response execution; longer tasks get cut off.- CPU-heavy background work still counts against the CPU time limit.
- Multiple
ctx.waitUntil()calls are fine and run concurrently.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_mZIBoWitozo8B546bUF1RQ