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

> Learn how the heartbeat mechanism in Cloudflare's Computer sync protocol detects stale connections with periodic pings. Discover how it prevents timeouts and triggers automatic reconnection.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: internals
- Published: 2026-08-07

---

**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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/heartbeat.ts)**. This module implements:

- Periodic `setInterval` scheduling
- Promise-based ping execution with rejection handling
- Safe cleanup via returned `stop()` function

```typescript
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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/container/cloudflare-container.ts)** instantiates and manages the heartbeat lifecycle:

```typescript
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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/transport-failure.ts)** module provides the glue between heartbeat failures and workspace recovery:

```typescript
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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/transport-failure.ts) classification and `BackendHandle` invalidation
- The default **20-second interval** balances quick detection against minimal overhead
- Container backends in [`cloudflare-container.ts`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/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`](https://github.com/cloudflare/computer/blob/main/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.