cloudflare / cloudflare/workerd
🐛 Workflows: caught waitForEvent timeout emits false "Worker code had hung" cancellation
- Dominant language
- C++
- Stars
- 8.7k
- Forks
- 739
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 174
Description
## Summary
A standalone deployed Workflow reproduces a Workers runtime cancellation after a `waitForEvent()` timeout is caught and the Workflow `run()` method reaches its end. The HTTP API request succeeds, but the Workflow invocation is logged as:
> The Workers runtime canceled this request because it detected that your Worker's code had hung and would never generate a response.
The repro has no Sentry, RPC, queues, Durable Objects, `waitUntil()`, or application imports.
## Reproduction
Deployed repro:
https://workflow-timeout-repro.gizmo-ai.workers.dev
1. Run the timeout case:
```shell
curl 'https://workflow-timeout-repro.gizmo-ai.workers.dev/?mode=timeout'
```
2. The request returns HTTP 200 with a Workflow instance ID.
3. The Workflow waits for an event with a one-second timeout. No event is sent.
4. The timeout is caught, the Workflow logs `repro timeout caught`, then logs `repro workflow finished`.
5. The Workers logs then contain the cancellation above.
For comparison, this case sends the matching event after 100 ms and completes without the cancellation:
```shell
curl 'https://workflow-timeout-repro.gizmo-ai.workers.dev/?mode=event'
```
## Minimal repro
```ts
export class TimeoutWorkflow extends WorkflowEntrypoint {
async run(_event: WorkflowEvent, step: WorkflowStep): Promise {
try {
await step.waitForEvent('repro wait', {
type: 'workflow-timeout-repro-event',
timeout: '1 second',
});
console.log('repro event received');
} catch (error) {
console.log('repro timeout caught', error);
}
console.log('repro workflow finished');
}
}
```
The fetch handler only creates the Workflow instance and returns JSON. The deployed worker is a standalone reproduction; its source can be provided privately if needed.
## Expected behavior
Once the `waitForEvent()` timeout rejects, the rejection is caught and `run()` returns normally. The Workflow should complete without a runtime hung-request cancellation.
## Environment
- Deployed Worker: `workflow-timeout-repro`
- Compatibility date: `2026-08-06`
- Cloudflare Workflows on Workers
- Reproduces on the deployed runtime; the event-success path is clean
Could this be a timeout-cleanup issue where `waitForEvent()` leaves a pending resolver, event listener, or other promise associated with the Workflow invocation after its timeout has been caught?
Contributor guide
Research direction
Start with the deployed workflow's fetch entry point and the TimeoutWorkflow.run reproduction, using the timeout and event curl modes to compare logs. Trace how waitForEvent handles the one-second timeout after the rejection is caught; done means the timeout path reaches workflow completion without the false hung-request cancellation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, typescript
- Domain
- backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100