Nested waitUntil() rejects during active background work after a Durable Object response returns
- Dominant language
- Rust
- Stars
- 4.6k
- Forks
- 171
- PR merge metrics
- No merged PRs in 30d
Description
**Version:** celld 0.4.1
A Durable Object registers background work with `ctx.waitUntil()` and returns its HTTP response. While that background work is still running, calling the imported `waitUntil()` from `cloudflare:workers` throws:
```text
Disallowed operation called within global scope.
```
This is active request-associated background work, not module/global initialization.
### Reduced reproduction
```js
import { waitUntil } from "cloudflare:workers";
export class Repro {
constructor(ctx) {
this.ctx = ctx;
}
fetch() {
this.ctx.waitUntil(
(async () => {
await new Promise((resolve) => setTimeout(resolve, 10));
// Runs after the response returns, while the outer task is pending.
const pending = fetch("https://example.com");
waitUntil(pending); // Throws the global-scope error.
})(),
);
return new Response("accepted");
}
}
```
### Expected behavior
Additional `waitUntil()` work can extend the lifetime while previously registered background work remains pending.
### Actual behavior
Registration is rejected after the response returns. Once the original background task settles, unfinished native operations are canceled.
### Impact
Our background OpenTelemetry exporter catches the registration exception and returns without awaiting its export. Celld then cancels both the exporter fetch and its timeout. The collector can receive spans followed by `context canceled`, while the application receives neither an export result nor a timeout.
### Verified workaround
Return the export promise from our observer callback, allowing the already-registered outer background task to await it.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the reduced Durable Object reproduction and the `ctx.waitUntil()` and imported `cloudflare:workers` `waitUntil()` entry points described in the issue. Trace why registration is treated as global scope after the response returns, then verify that nested work remains registered while the outer background task is pending and that the cancellation behavior no longer occurs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, rust
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100