cloudflare / cloudflare/workerd

🐛 Workflows: caught waitForEvent timeout emits false "Worker code had hung" cancellation

Open
#6,925 3 comments 0 reactions 0 assignees View on GitHub
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

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.