cloudflare / cloudflare/workers-sdk
🐛 BUG: Server Sent Events (SSE) causes `Error: The script will never generate a response.`
- Dominant language
- TypeScript
- Stars
- 4.5k
- Forks
- 1.5k
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 187
Description
### Which Cloudflare product(s) does this pertain to?
Wrangler, Miniflare, Pages
### What versions are you using?
3.99.0 [wrangler], 3.20241218.0 [miniflare],
### What operating system and version are you using?
Ubuntu [WSL]
### Please provide a link to a minimal reproduction
_No response_
### Describe the Bug
I have implemented server sent events (SSE) in a Remix/React Router v7 app using `remix-utils` and node native, `EventEmitter`:
```typescript
import { EventEmitter } from "node:events";
import { eventStream } from "remix-utils/sse/server";
import type { Route } from "./+types/seed.logs";
export const seedEmitter = new EventEmitter();
export async function loader({ request }: Route.LoaderArgs) {
let batchedLogs: string[] = [];
let timeoutId: NodeJS.Timeout;
return eventStream(request.signal, function setup(send, abort) {
function handleLog(message: string) {
batchedLogs.push(message);
// Clear existing timeout
if (timeoutId) clearTimeout(timeoutId);
// Send batched logs every 250ms
timeoutId = setTimeout(() => {
if (batchedLogs.length > 0) {
send({ event: "log", data: JSON.stringify(batchedLogs) });
batchedLogs = [];
}
}, 250);
}
seedEmitter.on("log", handleLog);
return function cleanup() {
seedEmitter.off("log", handleLog);
if (timeoutId) clearTimeout(timeoutId);
abort();
};
});
}
```
The event emitter is shared and can be triggered elsewhere in the app. the `handleLog `function queues up messages for 250ms
When running this under node (eg `react-router dev`) it works perfectly - events stream to UI as expected (using `useEventSource `from remix-utils)
When building and running under `workerd `(eg `wrangler pages dev`) I get the errors:
```
[wrangler:inf] GET /admin/seed/logs 200 OK (13ms)
✘ [ERROR] Uncaught (in response) Error: The script will never generate a response.
✘ [ERROR] A hanging Promise was canceled. This happens when the worker runtime is waiting for a Promise from JavaScript to resolve, but has detected that the Promise cannot possibly ever resolve because all code and events related to the Promise's I/O context have already finished.
✘ [ERROR] workerd/server/server.c++:3898: error: Uncaught exception: workerd/io/io-context.c++:1209: failed: remote.jsg.Error: The script will never generate a response.
stack:
/home/user/code/remix-project/node_modules/.pnpm/@cloudflare+workerd-linux-64@1.20241218.0/node_modules/@cloudflare/workerd-linux-64/bin/workerd@286cb40
```
This seems like a bug to me as `EventEmitter `and `ReadableStream`/streaming responses are officially supported (and working in remix/react-router7 generally as streaming promises works as expected).
I have tried adding a ctx.waitUntil in the functions handler, but this doesnt seem to do anything here
For completeness, this is my `[[path]].ts` file:
```typescript
import { createPagesFunctionHandler } from "@react-router/cloudflare";
import { getLogger } from "../app/services/logger.server";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
// eslint-disable-next-line import/no-unresolved
import * as build from "../build/server";
import { getLoadContext } from "../load-context";
export const onRequest = async ({
request,
...ctx
}: EventContext) => {
const logger = getLogger({
request,
context: { cloudflare: { ctx, env: ctx.env } },
});
const response = createPagesFunctionHandler({
// @ts-expect-error - the types are not correct
build,
getLoadContext,
})({ request, ...ctx });
ctx.waitUntil(logger.flush());
return response;
};
```
Any help/guidance is welcome. This works so nicely... when it works
### Please provide any relevant error logs
```
[wrangler:inf] GET /admin/seed/logs 200 OK (13ms)
✘ [ERROR] Uncaught (in response) Error: The script will never generate a response.
✘ [ERROR] A hanging Promise was canceled. This happens when the worker runtime is waiting for a Promise from JavaScript to resolve, but has detected that the Promise cannot possibly ever resolve because all code and events related to the Promise's I/O context have already finished.
✘ [ERROR] workerd/server/server.c++:3898: error: Uncaught exception: workerd/io/io-context.c++:1209: failed: remote.jsg.Error: The script will never generate a response.
stack:
/home/user/code/remix-project/node_modules/.pnpm/@cloudflare+workerd-linux-64@1.20241218.0/node_modules/@cloudflare/workerd-linux-64/bin/workerd@286cb40
```

Contributor guide
Research direction
Start with the SSE loader using remix-utils, EventEmitter, and ReadableStream behavior under workerd, then compare it with the working react-router dev path. Reproduce with wrangler pages dev using the versions listed and inspect the [[path]].ts handler and generated build/server entry point. Done means the SSE endpoint streams events without the “script will never generate a response” or hanging-Promise errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, react, typescript
- Domain
- backend, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100