cloudflare / cloudflare/agents
No supported way to assert a Durable Object's hibernation state / eligibility in tests
- Dominant language
- TypeScript
- Stars
- 5.6k
- Forks
- 711
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 53
Description
## Summary
Durable Objects bill for **wall-clock residency**. WebSocket Hibernation avoids that for idle, connected DOs — but hibernation is silently disqualified while the isolate has **any pending `setTimeout` / `setInterval`**. A single keepalive timer (easy to pull in via a dependency) therefore pins a DO resident and bills full residency for every live connection, with **no error and nothing observable in the connection's behavior**.
I wanted a CI regression test for "this DO is still hibernation-eligible." It turns out you *can* write one — but only by instrumenting the DO itself with a durable reconstruction counter and idling past workerd's ~10s inactivity timer in real time. There is no supported way to simply *ask* whether a DO hibernated or is still eligible.
_Provenance: originally hit in a real sync Durable Object where a dependency's `setInterval` keepalive silently blocked hibernation — livestorejs/livestore#1328, whose thread independently asks for the same thing: "an API of any sort to check DO state (hibernated or not) as replacement for [the] current probe."_
## Reproduction
Minimal repo: **https://github.com/bohdanbirdie/cf-do-hibernation-repro** — a plain DO (`PlainRoom`) and the same DO holding one `setInterval(() => {}, 2**31-1)` (`TimerRoom`). The DO is instrumented with a durable `boots` counter (incremented in the constructor, read over a normal request), and driven via `SELF` under `@cloudflare/vitest-pool-workers`. Across a 20s idle window:
```
plain DO: boots 1 -> 2 # reconstructed after idle => hibernated
timer DO: boots 1 -> 1 # never reconstructed => pinned by one setInterval
```
`npm test` — control + plain-hibernates + timer-pinned, **3/3 passing, reproducible across runs**.
Versions: `@cloudflare/vitest-pool-workers@0.18.5`, `vitest@4.1.10`, `miniflare@4.20260710.0`, `workerd@1.20260710.1`.
So the platform models timer-pinning correctly and the regression **is** catchable — the problem is what it costs to catch it.
## Why it's awkward
1. **You must instrument production code to observe hibernation at all.** There's no supported "did it hibernate / is it eligible" signal, so the DO has to carry a durable reconstruction counter purely for the test. Cloudflare's own `y-partyserver` does exactly this — [`YHibernateTracker`](https://github.com/cloudflare/partykit/blob/main/packages/y-partyserver/src/tests/worker.ts) counts `onStart` calls in `ctx.storage`, and its `hibernation.test.ts` drives a real `wrangler dev` subprocess it kills/restarts rather than asserting hibernation in-process.
2. **A plain probe can't tell you either — reading a DO wakes it.** The counter above only works because it persists the reconstruction count to storage; a direct check would transition the DO back to active and report the wrong state. This is the observer effect @MattieTK describes in [workerd#5835](https://github.com/cloudflare/workerd/issues/5835) ("Durable Object Lifecycle Event Emission for Local Dev Tooling") — the same need, from the runtime/tooling side.
3. **`evictDurableObject` doesn't cover it.** It *forces* hibernation, bypassing the eligibility gate — so it structurally can't detect a pending-timer regression, which is the exact failure that matters.
(There's also a wall-clock cost — every hibernation test idles ~10s of real time because the inactivity timer can't be advanced — but that's secondary.)
## What I'm asking for
Some supported way to observe or assert a DO's hibernation state / eligibility from a test — without having to instrument the DO, and without the probe itself waking it. I'm not proposing a specific API shape; workerd#5835 already lays out this need on the runtime side, and this is its test-time counterpart. Raising it here since the Agents SDK depends on hibernatable DOs and hits hibernation issues directly (e.g. agents#996).
cc @threepointone — filing here as we discussed in person. Thanks!
Contributor guide
Research direction
Start with the linked reproduction and its three Vitest cases, then read workerd#5835 and y-partyserver's packages/y-partyserver/src/tests/hibernation.test.ts and worker.ts. Define a supported test-time observation or assertion that does not instrument the Durable Object or wake it; done should cover eligibility and hibernation without relying on a real-time reconstruction counter.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend, cloud, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100