README.md— what this repo is and how the pieces fit together.CONTRIBUTING.md— setup, checks, commit and pull request conventions. The canonical source for the day-to-day workflow.docs/README.md— design specification. Forward- looking; treat as intent, not as a description ofmain.docs/08_capnweb_interface.md— the RPC contract between the Durable Object andwsd. Required reading before touchingpackages/rpcorpackages/workspace.- Each package's own
README.md— implementation status and package-specific notes.
In-repo skills live under .agents/skills/. Load
the file directly when the trigger applies:
| Skill | Load when |
|---|---|
prose |
Writing code comments, commit messages, READMEs, or documentation. |
pull-requests |
Writing or editing a pull request description. |
test-driven-development |
Implementing logic, fixing a bug, or changing behavior. |
capnweb |
Touching anything that crosses the RPC boundary: packages/rpc, packages/workspace, the wsd client, or the Durable Object server. |
cloudflare |
Index of host-side Cloudflare skills — Workers, Durable Objects, wrangler, sandbox SDK, agents SDK. |
A fresh container does not have everything the tests need. The traps below cost real time if you discover them one failure at a time.
Native build tools. packages/wsd depends on fuse-native, a
native addon. Building it needs a C toolchain and the libfuse2 headers.
On Debian or Ubuntu:
apt-get install build-essential libfuse-devIf the fuse-native build fails, npm install aborts the whole
install, not just that one package. When you only need the rest of the
workspace, install with npm install --ignore-scripts to skip the
native build.
arm64 hosts. fuse-native ships a prebuilt libfuse for x64 only.
On a Linux arm64 host or container (including a Linux container on
Apple Silicon, or arm64 CI) the link fails with file in wrong format. The path below is Debian or Ubuntu arm64; a native macOS host
uses macFUSE instead and does not hit this. Replace the bundled library
with the system one and rebuild:
cp /usr/lib/aarch64-linux-gnu/libfuse.so.2 \
node_modules/fuse-shared-library-linux/libfuse/lib/libfuse.so
cd node_modules/fuse-native && npx node-gyp rebuildBuild before you test. The test scripts don't build the sibling
packages first. Several suites need build output that is absent in
a clean checkout: packages/wsd imports the sibling @cloudflare/dofs
and @cloudflare/workspace-rpc packages from their dist/
directories, packages/wsd's src/cli/wsd.test.ts spawns the bundled
CLI at dist/cli/wsd.cjs, and examples/think-compare-runtimes
imports @cloudflare/workspace/backends/container, which exists only
after the workspace package is built. Run npm run build across the
workspace before npm test on a clean checkout.
Real FUSE needs privilege. packages/wsd's src/cli/wsd.test.ts
runs its real-FUSE case only when /dev/fuse is reachable; otherwise
it resolves to the shim and skips. The guard is a bare existence check,
so a mknod'd /dev/fuse in an unprivileged container defeats the
skip and the mount then fails with EPERM, turning a clean skip into a
hard failure. Leave the device absent unless the container is
privileged (--privileged, or CAP_SYS_ADMIN with device access). The
src/exec/runner.fuse.test.ts suite is separate: it skips unless both
Docker and the prebuilt wsd binary are available, and runs wsd
inside a privileged container. See the
debugging-wsd-fuse skill
for the privileged Docker setup.
Run from the repo root:
npm run format # biome format --write .
npx biome check . # biome lint + formatter verificationbiome check must exit zero. Fix the underlying issue rather than
silencing the rule.
Then run the package-level tests for whatever you touched:
npm test # whole workspace
npm test --workspace @cloudflare/dofs # one package
npm test --workspace @cloudflare/dofs -- src/foo.test.ts # one fileFull details, including typecheck and build commands, are in
CONTRIBUTING.md.
Follow CONTRIBUTING.md. The short version:
- One logical change per commit.
- Imperative subject prefixed with the scope (
dofs:,rpc:,wsd:,workspace:,examples/think:,docs:,ci:). Multiple scopes joined with commas. - Self-contained body wrapped at 72 characters. No references to chat history, agent sessions, sibling commit SHAs, or task identifiers.
- No emojis. No marketing voice. Prose paragraphs in the body, not bulleted lists.
Full guidance is in .agents/skills/prose/SKILL.md
and .agents/skills/pull-requests/SKILL.md.
Two top-level directories hold helper scripts. They're outside the workspace package set on purpose — they're tooling, not shipped code.
script/ holds maintainance scripts and operator-facing harnesses for wsd and the sync
loop. Reach for these when you're chasing a behavior the unit tests don't cover.
set-versions.mjssyncs the version of every published package in lockstep. Invoked from the release pipeline.shellboots a debian-slim container with the linuxwsdbinary mounted under/usr/local/bin. The starting point for anything that needs a real FUSE mount.wsd-soak.mjsboots twowsdcontainers wired peer-to-peer and soaks the sync loop. Use it to chase convergence or churn bugs.wsd-stub-soak.mjssoaks the long-lived WebSocket session and readssession.getStats()to detect stub-disposal drift. Run it for changes around the capnweb lifecycle.wsd-fuse-flush.mjsend-to-end checks that the FUSE driver spills its in-memory write buffer into the backing VFS, so a capnweb-sidepullOnceactually sees the bytes.fs-tests.sh/run-fs-tests.shrun the filesystem conformance harness against the FUSE mount.fs-bench.sh/run-fs-bench.shbenchmark common development tasks against the mount with a tmpfs baseline for comparison.exec-testsbootswsdin docker with FUSE disabled and exercises a fewshell.execscenarios.npm-bench.sh/run-npm-bench.shbenchmark npm package installs on native disk vs the FUSE mount.run-npm-bench.shis the user-facing entry point that boots a privileged Docker container;npm-bench.shruns inside it. SetSCENARIOS,REPS,WARMUP, andOUTPUT_JSONto control what runs and where results land.
When you add a script, drop a one-line description at the top of the file and add it to the list above.
- RPC. The wire contract is capnweb. Read
docs/08_capnweb_interface.mdand thecapnwebskill before changing anything that crosses the Durable Object ↔wsdboundary. Stubs are object-capabilities; dispose them. Leaks are tracked by the harness inpackages/rpc. - Storage.
packages/dofsis the authoritative SQLite layer. Filesystem primitives operate on aDatabasehandle; the sync protocol building blocks share the same handle. The package README enumerates the exported surface. - FUSE shim.
packages/wsdruns in the sandbox container. FUSE- backed tests only run on Linux and are skipped elsewhere automatically. - Examples are real consumers.
examples/think,examples/container, andexamples/workerexercise the public surface. If you change a public API, update them in the same change.