Disclosure: This report was researched and written by an AI agent (Claude, via Claude Code) while integrating Cloudflare Computer into a real project, under human supervision. All observations below come from live deployments against production Workers + Containers; error messages and counts are copied from real runs, not synthesized. The human maintainer of our project reviewed this report before filing.
Describe the bug
Sync between the Workspace DO and a container can permanently wedge with parent directory missing: <child path> when directory entries stream after their children in the sync entry stream. We hit this in two mirrored forms:
A. Pull side (out-of-bracket directory creation). A long-running sidecar process in the container (in our case Claude Code, writing its ~/.claude state tree onto the FUSE mount outside any runtime.exec bracket) creates new directories and files. The next pull fails:
WorkspaceFsError: parent directory missing: /workspace/.claude/sessions/42.33a33b3c…e1317e2995672ecff90f1da.key: /workspace/.claude/sessions/42.…key
and — importantly — every subsequent pull fails on the same entry, so sync silently stops for the workspace from that point on. Post-exec pulls report sync.status: "pending" forever; nothing written after the wedge ever reaches the authoritative store. We only noticed when a container replacement lost data.
B. Push side (replacement container). After the container is replaced (kill 1 in our test; watermarks are correctly reset by reconcileWatermarks on reconnect — we verified the rev-0 rebaseline happens), the full-tree push to the fresh container fails with the same error for a child entry whose parent directory entry had not been applied yet. This wedge is worse: the pre-command push is a hard gate, so every runtime.exec fails, and there is no channel left to fix the container (healing it would need an exec, and execs need the push). Pulls kept working in this state, only pushes were stuck.
Root cause (hypothesis, consistent with everything we observed)
docs/02_sync_protocol.md says entries stream "ordered by rev then path". A directory's entry appears to be re-recorded with a new rev whenever the directory itself changes — e.g. its mtime bumps because files were added to it. So a parent directory that keeps gaining children ends up with a higher rev than some of its existing children, and streams after them. The apply side (both computerd and the DO's pull apply) then fails on a child whose parent entry hasn't arrived, instead of either creating the parent implicitly or deferring the child.
Two data points that support this:
- DO-side inspection at wedge time showed a perfectly normal tree:
/workspace/.claude/sessions existed as a regular directory containing the exact .key file the push was failing on. Nothing was missing — only the ordering was wrong.
- Both of our workarounds operate purely on entry revs/existence and immediately unwedge sync (details below).
Expected behavior
Either of:
- the apply side creates missing parent directories implicitly (
mkdir -p semantics) or defers child entries until their parent has been applied; or
- the stream orders entries so a parent directory is always applied before its children (e.g. secondary sort by path depth, or don't re-stamp a directory's rev on child-driven mtime changes);
and in all cases: a failed pull should not permanently wedge — today the same entry re-fails on every retry with no operator-visible signal (the exec result's sync.status: "pending" is easy to miss and the pending-sync retry hits the same error).
Steps to reproduce
Starting from examples/container (deployed to real Workers + Containers, not wrangler dev):
- Deploy, then start a detached process in the container that creates a new directory tree with files, outside any exec bracket, e.g.:
POST /c/demo/exec {"command":"setsid sh -c 'mkdir -p /workspace/newdir && for i in 1 2 3; do sleep 3; echo x > /workspace/newdir/f$i; done' < /dev/null > /dev/null 2>&1 & echo started"}
(Any agent-like sidecar reproduces this naturally; Claude Code's ~/.claude tree triggered it for us within a minute of booting.)
- After the writer has created files, run any exec (to trigger a pull), then call
workspace.pull() — it fails with parent directory missing: /workspace/newdir/f2 (or similar), and keeps failing on every retry. Wedge A.
- To reproduce wedge B: get a tree where a directory gained children over time (several pulls interleaved with new files landing in the same dir — Claude Code's session/lock files did this for us: dir created early,
.key files added later, dir mtime → new rev). Then force a container replacement (kill 1 via exec) and run any exec against the fresh container: the rev-0 full push fails with parent directory missing for one of those children, and all execs are now stuck.
Workarounds we verified live (in case they help confirm the diagnosis)
- Pull wedge: parse the failing path from the error,
workspace.fs.mkdir(dirname, { recursive: true }) on the DO, retry the pull in a loop. Converges (each round heals one directory). Also, pre-creating the sidecar's expected directory tree inside an exec bracket at boot prevents form A entirely.
- Push wedge: read the failing entry's bytes DO-side and rewrite them unchanged (
fs.readFile → fs.writeFile) — this stamps the child with a fresh rev above its parent's, restoring parent-before-child order — then retry the exec. After one heal round our previously-bricked replacement container applied the full push (pushed: 680) and execs recovered.
The push-side rewrite trick working is, we think, the strongest evidence for the rev-ordering diagnosis.
Environment
@cloudflare/computer 0.2.1 (npm), container backend (CloudflareContainerBackend, egress: { mode: "direct" })
computerd from ghcr.io/cloudflare/computer-computerd-linux-x64:0.2.1, laid over debian:stable-slim with fuse3/libfuse2t64 per the example Dockerfile (FUSE_MOUNT=auto, real FUSE in production)
- Deployed Cloudflare Workers + Containers (
instance_type: standard-1), compatibility_date 2026-05-26, nodejs_compat
- DO:
withWorkspace(withWorkspaceContainer(DurableObject)), SQLite-backed class, exactly the examples/container wiring plus long-running sidecar processes in the container
Describe the bug
Sync between the Workspace DO and a container can permanently wedge with
parent directory missing: <child path>when directory entries stream after their children in the sync entry stream. We hit this in two mirrored forms:A. Pull side (out-of-bracket directory creation). A long-running sidecar process in the container (in our case Claude Code, writing its
~/.claudestate tree onto the FUSE mount outside anyruntime.execbracket) creates new directories and files. The next pull fails:and — importantly — every subsequent pull fails on the same entry, so sync silently stops for the workspace from that point on. Post-exec pulls report
sync.status: "pending"forever; nothing written after the wedge ever reaches the authoritative store. We only noticed when a container replacement lost data.B. Push side (replacement container). After the container is replaced (
kill 1in our test; watermarks are correctly reset byreconcileWatermarkson reconnect — we verified the rev-0 rebaseline happens), the full-tree push to the fresh container fails with the same error for a child entry whose parent directory entry had not been applied yet. This wedge is worse: the pre-command push is a hard gate, so everyruntime.execfails, and there is no channel left to fix the container (healing it would need an exec, and execs need the push). Pulls kept working in this state, only pushes were stuck.Root cause (hypothesis, consistent with everything we observed)
docs/02_sync_protocol.mdsays entries stream "ordered byrevthenpath". A directory's entry appears to be re-recorded with a new rev whenever the directory itself changes — e.g. its mtime bumps because files were added to it. So a parent directory that keeps gaining children ends up with a higher rev than some of its existing children, and streams after them. The apply side (bothcomputerdand the DO's pull apply) then fails on a child whose parent entry hasn't arrived, instead of either creating the parent implicitly or deferring the child.Two data points that support this:
/workspace/.claude/sessionsexisted as a regular directory containing the exact.keyfile the push was failing on. Nothing was missing — only the ordering was wrong.Expected behavior
Either of:
mkdir -psemantics) or defers child entries until their parent has been applied; orand in all cases: a failed pull should not permanently wedge — today the same entry re-fails on every retry with no operator-visible signal (the exec result's
sync.status: "pending"is easy to miss and the pending-sync retry hits the same error).Steps to reproduce
Starting from
examples/container(deployed to real Workers + Containers, notwrangler dev):~/.claudetree triggered it for us within a minute of booting.)workspace.pull()— it fails withparent directory missing: /workspace/newdir/f2(or similar), and keeps failing on every retry. Wedge A..keyfiles added later, dir mtime → new rev). Then force a container replacement (kill 1via exec) and run any exec against the fresh container: the rev-0 full push fails withparent directory missingfor one of those children, and all execs are now stuck.Workarounds we verified live (in case they help confirm the diagnosis)
workspace.fs.mkdir(dirname, { recursive: true })on the DO, retry the pull in a loop. Converges (each round heals one directory). Also, pre-creating the sidecar's expected directory tree inside an exec bracket at boot prevents form A entirely.fs.readFile→fs.writeFile) — this stamps the child with a fresh rev above its parent's, restoring parent-before-child order — then retry the exec. After one heal round our previously-bricked replacement container applied the full push (pushed: 680) and execs recovered.The push-side rewrite trick working is, we think, the strongest evidence for the rev-ordering diagnosis.
Environment
@cloudflare/computer0.2.1 (npm), container backend (CloudflareContainerBackend,egress: { mode: "direct" })computerdfromghcr.io/cloudflare/computer-computerd-linux-x64:0.2.1, laid overdebian:stable-slimwithfuse3/libfuse2t64per the example Dockerfile (FUSE_MOUNT=auto, real FUSE in production)instance_type: standard-1),compatibility_date 2026-05-26,nodejs_compatwithWorkspace(withWorkspaceContainer(DurableObject)), SQLite-backed class, exactly theexamples/containerwiring plus long-running sidecar processes in the container