# How the Computer Sync Protocol Handles Conflict Resolution with Its Change Coalescing Algorithm

> Learn how the Computer sync protocol resolves filesystem conflicts. Discover its change coalescing algorithm and last-writer-wins strategy for efficient mutation handling.

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

---

**The Computer sync protocol resolves filesystem conflicts by coalescing all mutations per path to the highest revision and applying a last-writer-wins rule when batches arrive at the Durable Object.**

The **Computer** project from Cloudflare implements a distributed filesystem that synchronizes state between **Durable Objects (DOs)** and container-side **FUSE mounts**. A critical component of this system is how it handles **conflict resolution** when multiple containers modify the same path concurrently. This article examines the **change coalescing algorithm** at the heart of the sync protocol and explains how it deterministically resolves conflicts without merge operations or error states.

## What Is Change Coalescing in the Computer Sync Protocol?

At its core, **change coalescing** reduces multiple filesystem operations on the same path into a single representative entry. Rather than streaming every `write`, `rename`, or `unlink` across the wire, the protocol compresses history into the *latest state* per path.

This design serves two purposes: it minimizes network payload and simplifies conflict resolution by ensuring only one mutation per path competes at the DO layer.

## How coalesceChanges Implements Per-Path Reduction

The algorithm lives in [[`packages/dofs/src/sync/coalesce.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/sync/coalesce.ts)](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/sync/coalesce.ts). The `coalesceChanges` function accepts a **cursor** `{ rev, path? }` and yields a stream of `ChangeEntry` objects ordered by `(rev, path)`.

The implementation follows six distinct phases:

1. **Collect live mutations** — Scan `vfs_nodes` for the highest `rev` per path touched after the cursor
2. **Collect tombstones** — Scan `vfs_changes` where `op = 'delete'` for deletions at the highest `rev` per path
3. **Merge and pick highest revision** — For each path, keep only the candidate with the greatest `rev`
4. **Apply ignore list** — Drop paths matching `ignore` patterns (e.g., `node_modules`) before yielding
5. **Sort deterministically** — Order remaining candidates by `(rev, path)` for safe checkpointing
6. **Materialize entries** — Call `materialiseChange` to read current VFS state and emit `ChangeEntry` objects

The **cursor-based streaming** model guarantees that receivers can advance their fetch position after each batch. If a path is rewritten after the scan, its entry is simply omitted now and re-delivered later—preserving eventual convergence.

## Conflict Resolution: Last Writer Wins

When two containers edit the same path concurrently, **conflict resolution** is handled through arrival order at the DO, not through merge logic. As documented in [[`docs/02_sync_protocol.md`](https://github.com/cloudflare/computer/blob/main/docs/02_sync_protocol.md)](https://github.com/cloudflare/computer/blob/main/docs/02_sync_protocol.md), the protocol applies batches in the order received.

The DO does not detect that the same path changed on both sides. Each container pushes its **coalesced batch** (one entry per path at the highest local revision). The later batch overwrites the earlier one. No error is produced; no three-way merge occurs. The final state equals the most recent `ChangeEntry` received.

This **last-writer-wins** semantics is a deliberate trade-off. It keeps the protocol stateless at the DO layer and avoids the complexity of operational transforms or CRDTs for filesystem data.

## Idempotence and Duplicate Suppression

The DO's `applyChanges` routine implements **idempotence** through duplicate detection. An `alreadyApplied` check drops entries that were previously processed, making re-application cheap and safe. This matters for reliability: cursors can retry without corrupting state.

## Practical Examples

### Concurrent Writes to the Same File

```typescript
import { Workspace } from "@cloudflare/computer";

const ws = new Workspace(/* workspace config */);

// Container A writes first
await ws.fs.writeFile("/shared/config.json", JSON.stringify({ version: "A" }));
await ws.push(); // pushes rev = 1

// Container B writes concurrently
await ws.fs.writeFile("/shared/config.json", JSON.stringify({ version: "B" }));
await ws.push(); // pushes rev = 2, arrives later at DO

// Final state reflects B's write
await ws.pull();
const config = await ws.fs.readFile("/shared/config.json", "utf8");
console.log(JSON.parse(config).version); // → "B"

```

Each `push` internally streams `coalesceChanges`, yielding one entry per path. The higher-revision entry from Container B overwrites Container A's entry at the DO.

### Direct Coalescing API

```typescript
import { openDatabase } from "@cloudflare/dofs";
import { coalesceChanges } from "@cloudflare/dofs/sync/coalesce";

const db = await openDatabase("workspace.db");

// Simulate rapid successive writes
await db.exec(`INSERT INTO vfs_nodes (inode, rev, path) VALUES (1, 1, '/data.txt')`);
await db.exec(`INSERT INTO vfs_nodes (inode, rev, path) VALUES (1, 2, '/data.txt')`);
await db.exec(`INSERT INTO vfs_nodes (inode, rev, path) VALUES (1, 3, '/data.txt')`);

// Coalesce from the beginning
for await (const entry of coalesceChanges(db, { rev: 0, path: null })) {
  console.log(entry.rev, entry.path); // Only entry: rev = 3, /data.txt
}

```

Despite three mutations, `coalesceChanges` yields a single entry at the highest revision, demonstrating the **per-path coalescing** rule.

### Excluding Generated Directories

```typescript
for await (const entry of coalesceChanges(db, { rev: 0, path: null }, {
  ignore: ["node_modules/**", ".git/**", "*.log"]
})) {
  // Entries matching these patterns never cross the wire
}

```

The `ignore` option prevents large generated artifacts from consuming bandwidth and cursor positions.

## Key Source Files and Their Roles

| File | Purpose |
|------|---------|
| [[`packages/dofs/src/sync/coalesce.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/sync/coalesce.ts)](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/sync/coalesce.ts) | Core **coalescing algorithm** that compresses mutations per path |
| [[`docs/02_sync_protocol.md`](https://github.com/cloudflare/computer/blob/main/docs/02_sync_protocol.md)](https://github.com/cloudflare/computer/blob/main/docs/02_sync_protocol.md) | Documents cursor semantics, streaming protocol, and **conflict resolution** behavior |
| [[`packages/rpc/src/sync-driver.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts)](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts) | RPC layer that streams coalesced batches between container and DO |
| [[`docs/01_vfs.md`](https://github.com/cloudflare/computer/blob/main/docs/01_vfs.md)](https://github.com/cloudflare/computer/blob/main/docs/01_vfs.md) | Describes the SQLite-backed VFS schema powering revision tracking |

## Summary

- **Change coalescing** collapses all mutations per path to the highest revision, minimizing sync payload
- **Conflict resolution** follows last-writer-wins: the DO applies batches in arrival order without merge logic
- **Cursor-based streaming** enables deterministic checkpointing and safe batch retries
- **Idempotence** through duplicate detection ensures reliable re-application of changes
- **Ignore patterns** filter noise paths before they reach the wire

The Computer sync protocol trades merge complexity for operational simplicity, achieving **eventual consistency** through ordered, coalesced, and idempotent change streams.

## Frequently Asked Questions

### Does the Computer sync protocol detect conflicts between concurrent writers?

No. The protocol does not detect that the same path was modified on multiple containers. Each container pushes its coalesced batch independently. The DO applies entries in arrival order, so the later batch simply overwrites the earlier one. This last-writer-wins semantics is intentional and documented in the protocol specification.

### What happens if a file is modified while coalesceChanges is running?

The algorithm reads from **current VFS state** rather than historical snapshots. If a path is rewritten after the scan phase, its `ChangeEntry` will be omitted from the current batch. The next sync cursor will pick up the newer revision, ensuring eventual consistency without blocking writers.

### How does the ignore pattern option affect conflict resolution?

Ignore patterns filter paths before they enter the coalesced stream. If a path matches an ignore pattern, it is never sent to the DO and therefore never participates in conflict resolution. This is useful for ephemeral directories like `node_modules` that should remain local to each container.

### Can the same ChangeEntry be applied multiple times safely?

Yes. The DO's `applyChanges` routine checks an `alreadyApplied` set and silently drops duplicates. This idempotence allows receivers to retry cursor positions without corrupting filesystem state, making the protocol resilient to network interruptions.