cloudflare / cloudflare/workers-sdk

🐛 BUG: Server Sent Events (SSE) causes `Error: The script will never generate a response.`

Open
#7,767 0 comments 5 reactions 0 assignees View on GitHub
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
```

![Image](https://github.com/user-attachments/assets/7b272b2f-d523-41cb-a7c4-72fc9f9e9d6c)

Contributor guide

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.