Heartbeat Mechanism in Computer's Sync Protocol: How Stale Connections Are Detected

The heartbeat mechanism in Cloudflare's Computer sync protocol is an application-level timer that periodically issues a lightweight sync.watermarks() RPC to detect dead peers and prevent middlebox timeouts, automatically triggering reconnection when pings fail.

The sync protocol powering Cloudflare's Computer workspaces relies on a robust heartbeat system to maintain reliable WebSocket connections between the client and container-based backends. This mechanism ensures that silently broken connections are discovered quickly rather than waiting for user-initiated operations to fail.

How the Heartbeat Mechanism Works

The heartbeat serves two critical functions in the sync protocol:

  • Dead peer detection — Forces connection teardown when the remote side becomes unresponsive
  • Middlebox keepalive — Prevents NATs and edge routers from dropping idle connections after 100–600 seconds

The implementation centers on packages/computer/src/heartbeat.ts, which exports the startHeartbeat function used by container backends to manage connection health.

Core Detection Flow

When a heartbeat is active, the following sequence occurs:

  1. Timer fires — setInterval invokes the supplied ping function every configured period (default 20 seconds)
  2. Ping succeeds — No action taken; the connection remains healthy
  3. Ping rejects — The catch block executes:
    • The timer is cleared and a stopped flag prevents further pings
    • onFailure is invoked with the error
    • The container backend closes the WebSocket (ws.close())
  4. Transport failure propagates — The closed socket triggers WorkspaceTransportError classification via packages/computer/src/transport-failure.ts, invalidating the cached BackendHandle

This design guarantees stale connection detection in O(interval) time rather than potentially minutes of silent failure.

Heartbeat Implementation in Key Files

heartbeat.ts: The Timer and Ping Orchestrator

The core heartbeat logic lives in packages/computer/src/heartbeat.ts. This module implements:

  • Periodic setInterval scheduling
  • Promise-based ping execution with rejection handling
  • Safe cleanup via returned stop() function
import { startHeartbeat } from "./heartbeat.js";

const stop = startHeartbeat({
  intervalMs: 20_000,                 // 20 seconds
  ping: async () => rpc.sync.watermarks(),
  onFailure: (err) => {
    console.error("heartbeat failed:", err);
    ws.close();                       // forces reconnection
  },
});

// Cleanup when heartbeat is no longer needed:
stop();

The watermarks RPC is intentionally lightweight, returning only three SQL scalars to minimize overhead while still exercising the full request/response path.

cloudflare-container.ts: Backend Integration

The container backend in packages/computer/src/backends/container/cloudflare-container.ts instantiates and manages the heartbeat lifecycle:

if (this.#options.heartbeatIntervalMs > 0) {
  stopHeartbeat = startHeartbeat({
    intervalMs: this.#options.heartbeatIntervalMs,
    ping: () => (stub as unknown as WorkspaceRPC).sync.watermarks(),
    onFailure: () => {
      try { ws.close(); } catch { /* already closed */ }
    },
  });
}

Setting heartbeatIntervalMs to 0 disables the heartbeat entirely, which is useful in testing scenarios or when operating behind connection managers with their own keepalive strategies.

transport-failure.ts: Failure Classification

The packages/computer/src/transport-failure.ts module provides the glue between heartbeat failures and workspace recovery:

import { isWorkspaceTransportFailure } from "./transport-failure.js";

try {
  await backend.rpc.someMethod();
} catch (e) {
  if (isWorkspaceTransportFailure(e)) {
    // Invalidate cached handle, trigger reconnect
    workspace.clearBackendHandle();
  }
  throw e;
}

This classifier distinguishes transport errors (including heartbeat-induced closes) from application-level failures, ensuring appropriate recovery actions.

Stale Connection Detection in Practice

When investigating sync protocol connectivity issues, understanding the heartbeat's role helps diagnose whether failures are:

  • Heartbeat-reported — Immediate detection via failed watermarks calls
  • User-operation-reported — Delayed detection when the next real RPC executes

The heartbeat's proactive approach eliminates the ambiguity of "silent" WebSocket deaths, where the TCP connection remains open but no data flows. This commonly occurs with:

  • Laptop sleep/wake cycles
  • Network interface changes
  • Container migrations
  • Cloudflare edge rerouting events

Configuring Heartbeat Behavior

The heartbeat interval is configurable per-workspace through backend options. The default 20-second balance provides:

Interval Detection Latency Middlebox Compatibility Overhead
5 s Fast Excellent Higher RPC load
20 s (default) Moderate Good Minimal
60 s Slow Marginal Very low
0 (disabled) None Requires external keepalive None

Production deployments typically use the default or slight variations based on observed middlebox behavior in their specific network topology.

Summary

  • The heartbeat mechanism in Computer's sync protocol is implemented in packages/computer/src/heartbeat.ts as a configurable periodic timer
  • Stale connection detection works by issuing lightweight sync.watermarks() RPCs and closing the WebSocket when they fail
  • Automatic recovery occurs through transport-failure.ts classification and BackendHandle invalidation
  • The default 20-second interval balances quick detection against minimal overhead
  • Container backends in cloudflare-container.ts integrate heartbeat lifecycle management with WebSocket control

Frequently Asked Questions

What RPC does the heartbeat use for health checks?

The heartbeat calls sync.watermarks(), which returns three SQL scalar values. This RPC is chosen specifically because it exercises the full sync protocol stack without triggering side effects or heavy computation, making it ideal for frequent health probes.

How does the heartbeat handle already-closed WebSockets?

The onFailure callback in cloudflare-container.ts wraps ws.close() in a try/catch block to gracefully handle cases where the socket was already closed by another path. This prevents exception propagation from masking the original failure reason.

Can the heartbeat interval be disabled or changed at runtime?

The interval is set during backend initialization via heartbeatIntervalMs in the options object. It cannot be changed dynamically for an active connection, but different workspace instances can use different values. Setting the interval to 0 completely disables heartbeat functionality.

What's the difference between the sync protocol heartbeat and the exec-side heartbeat in runner.ts?

The sync protocol heartbeat discussed here maintains the WebSocket between client and workspace container. A separate heartbeat mechanism in packages/computerd/src/exec/runner.ts monitors child processes spawned for code execution within the workspace. These serve different purposes and operate at different protocol layers.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →