Skip to content
OTFotf
All posts

How to recover Cloudflare Worker streams and WebSockets after runtime updates

D
DaveAuthor
6 min read
Dex, Luna, and Byte watch work shift from an older draining process to a newer runtime while Nova hovers between them.

Cloudflare has extended the grace period for pending requests and connections during Workers runtime updates from up to 30 seconds to up to 20 minutes. That gives long-running requests, streamed AI responses, and live WebSocket sessions more time to finish. It does not make a connection permanent: Cloudflare says unfinished work is terminated when the grace period ends, and WebSockets can disconnect before then.

Byte and Nova reconnect a client at its last acknowledged event checkpoint

For a production agent, plan for the disconnect as a normal recovery path. Keep the conversation or job state outside the Worker process, give each emitted event a durable sequence number, and let a reconnecting client resume after the last event it acknowledged. The runtime update can then interrupt a transport without erasing the user’s work.

What the longer grace period changes

Cloudflare’s October 12, 2026 changelog says pending requests and connections can now receive a grace period of up to 20 minutes during a Workers runtime update; previously it was up to 30 seconds. When the new runtime is ready, new incoming HTTP requests go to it. Processes on the previous runtime stop accepting new requests and drain work already in progress. The grace period applies to each previous process. When it expires, unfinished requests and open connections on that process are terminated.

That is useful headroom for a long response or an active session. It is not a promise that every request receives a full 20 minutes, nor a reason to make a client depend on a single connection. The same changelog specifically says WebSockets can disconnect before the grace period ends, including connections using the WebSocket Hibernation API. A client that assumes its socket will survive an update can still lose the stream midway through a turn.

Treat the extension as extra time to drain, not as your persistence strategy. A response that finishes inside the window is convenient. A response that does not should be resumable from application-owned state.

Separate the session from its connection

An open socket is a delivery channel. It should not be the only copy of an agent’s conversation, pending tool call, or generated output. Store the recoverable state using a durable store appropriate to the workload: for example, a Durable Object’s SQLite-backed storage for per-session state, plus any external database or object storage needed for larger artifacts.

Model output as an ordered event stream. A minimal event record might contain a session ID, monotonically increasing sequence number, event type, payload, and creation time. Persist an event before treating it as safely delivered. The client remembers the last sequence it processed and includes that cursor when it reconnects. The server replays events after that cursor, then continues with new ones.

This gives the reconnect a concrete meaning: “continue session S after event 184,” rather than “open another socket and hope the assistant remembers.” Keep the cursor scoped to the authenticated user and session. Do not accept a client-supplied session ID as authorization; validate ownership before replaying any data.

11 production screens. Login and database wired.

The SaaS Dashboard Kit includes login, a database, and a working dashboard. Live demo at saas.otf-kit.dev.

See the live demo

Make replay safe for streams and tools

Token streaming and tool execution have different recovery costs. If a client disconnects after receiving part of a text delta, the simplest safe behavior is often to replay from the last acknowledged event. Include stable event IDs so the client can discard duplicates. If your UI cannot roll back a partially displayed delta, define whether replay replaces the current assistant message or appends to it; otherwise a reconnect can visibly duplicate text.

Tool calls need stronger protection. Give each logical tool invocation an idempotency key derived from the session and call ID. Persist the call’s state—queued, running, completed, or failed—before retrying it. On reconnect, return a stored result for a completed call instead of executing the side effect again. For an operation that cannot be made idempotent, record enough state to reconcile its result before deciding whether to retry. “The socket closed” does not mean “the tool did not run.”

Dex and Nova review a completed tool action recorded once before its saved result is resumed

An illustrative protocol can look like this:

type AgentEvent = {
  id: number;
  type: "text_delta" | "tool_started" | "tool_result" | "turn_complete";
  payload: unknown;
};

// Persist before publishing. On reconnect, replay events with id > after.
async function publish(sessionId: string, event: AgentEvent) {
  await eventStore.append(sessionId, event);
  await subscribers.send(sessionId, event);
}

eventStore and subscribers above are placeholders for your own storage and transport layer, not Cloudflare APIs. The important ordering is persist first, then send. A production design also needs a retention policy, authorization checks, backpressure, and a clear acknowledgement rule.

Use Durable Object hibernation deliberately

Durable Objects can hibernate while supported server-side WebSockets remain connected when the object accepts them through the hibernation API. Cloudflare documents that an event can wake the object and re-run its constructor. This helps reduce in-memory residency for quiet sessions, but application state still needs an explicit home.

Do not confuse hibernation with protection from every runtime update. The changelog warns that WebSockets may disconnect before the update grace period ends, including connections using the Hibernation API. Treat hibernation as a lifecycle and efficiency feature, not your only recovery plan. Put durable conversation state and event history in storage that survives the process, and make the same reconnect protocol work whether the old socket stayed healthy or the client had to establish a new one.

Reconnect without creating a retry storm

When the connection closes unexpectedly, reconnect with exponential backoff and jitter, starting with a short delay and capping the maximum wait. Reset the backoff after a stable connection. Send the session identifier and last acknowledged event ID, authenticate the request again, and resume from the server’s durable log. If the server reports that the cursor is older than retained history, return an explicit “resume unavailable” state and offer a safe restart or summary path; silently starting a blank agent turn is confusing.

For HTTP streams, use the same checkpoint idea even if you do not use WebSockets. Give each run a stable ID, record its current phase and last committed output, and let a client query or resume that run after a failed response. Retry only operations designed to be safe to repeat. A longer drain window can reduce how often clients need this path, but the recovery protocol is what makes interrupted work understandable.

Test the interruption, not just the happy path

Exercise a client disconnect during a text stream, during a tool call, and after a tool result is committed but before the client acknowledges it. Verify that replay does not lose events or duplicate side effects. Test a Durable Object waking after hibernation, and test a newly opened connection after the old one closes. Confirm that an expired cursor gets an explicit response.

Cloudflare’s longer grace period is a practical improvement for work that can drain. The design decision for an agent builder is still straightforward: let requests finish when they can, but checkpoint the session so the user’s work can continue when they cannot. For a broader example of session recovery in an agent runtime, see our guide to resuming OpenCode sessions on Cloudflare.

If you are starting the product UI and API around this workflow, the SaaS Dashboard kit is an optional $99 source-code starting point; it does not include Cloudflare runtime-update recovery or agent-session wiring. The session store, event protocol, and reconnect path remain application work.

Sources

ai-toolsarchitectureagents
OTF SaaS Dashboard Kit

Ship the product, not the setup.

  • 11 production screens — auth, team, analytics, and settings
  • Login and a real database are wired in
  • AI configs pre-tuned so your agent extends instead of regenerates
Need more than components?

Full-stack kits.
Pay once, own the code.

SaaS Dashboard and Fitness include authentication and a database. Arcade is browse-first with a local demo session and no backend sign-in by default. Booking is in Preview; Marketplace is coming soon and not currently sold. The Bundle includes the three live kits.

Everything Bundle — $149See full pricing

Get the free AI configs pack

AI configs in every kit: CLAUDE.md, .cursorrules and task prompts. Drop them in and your AI tool instantly understands your project.

No spam. Unsubscribe any time.

Prefer the free SDK? Star it on GitHub →