Skip to content

Fix issue with file sync post container restart - #103

Merged
aron-cf merged 15 commits into
mainfrom
fix-container-restart
Aug 14, 2026
Merged

Fix issue with file sync post container restart#103
aron-cf merged 15 commits into
mainfrom
fix-container-restart

Conversation

@aron-cf

@aron-cf aron-cf commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Fixes #101

A container command uses the workspace in four steps:

save host files → copy them into the container → run the command → copy changed files back

After computerd stopped, the next command could reuse its dead connection or reach the replacement container before it was ready. The command failed immediately. A failed copy into the container was also ignored, so a command could run against an empty or outdated workspace.

The change fixes that flow in these steps:

  1. Recognize container startup, health, and broken-connection errors as temporary failures.
  2. Drop and close the dead connection before asking for another one.
  3. Wait for the replacement container to become healthy, reconnect, and check which files it has.
  4. Retry copying files into or out of the container once. These copies are safe to repeat because the workspace records completed progress.
  5. Require the copy into the container to succeed before starting a command.
  6. Retry a command only when the connection code proves it was never sent. If it may have started, or the connection breaks while reading output, report the failure instead of running it again.
  7. Keep a failed copy after a completed command pending so the existing durable scheduler can finish it later.

Verification passed with:

npm run check
npm run typecheck --workspace @cloudflare/computer
npm run build
npm test --workspace @cloudflare/computer
npm run test:harness --workspace @cloudflare/computer
SOAK_SYNC_TICKS=10 SOAK_FETCH_CALLS=10 SOAK_EXEC_CALLS=20 node script/computerd-stub-soak.mjs

The deployed Model Context Protocol example was also tested by saving host and container-written files, stopping PID 1, and immediately running another container-shell command. A new container became ready after about 33 seconds, its temporary marker was gone, and both files saved in Durable Object storage were present. The test files were then removed.

The documentation explains the retry rules and the remaining limit: files written only inside a container can still be lost until they are copied back. A patch changeset is included. The shared protocol and computerd image are unchanged, so consumers only need to update @cloudflare/computer and redeploy their Worker and Durable Object code.

Mark container start, health, callback, and WebSocket upgrade
failures as transport errors while preserving their causes. Separate
locally disposed capnweb stubs from ambiguous session failures so
command recovery only retries requests that were never dispatched.
Run push and pull through one bounded reconnect attempt. Close stale
or unpublished handles before reconnecting, wait for teardown across
concurrent callers, and reconcile replacement watermarks before
replaying idempotent sync work.
Require the pre-exec push to finish successfully before dispatching a
command. This prevents execution against stale or incomplete container
contents instead of silently reporting a zero-entry push.
Resolve the active shell handle after pre-exec sync and for every
process lifecycle call. Retry command spawn only when the local RPC
layer proves dispatch never started. Ambiguous and mid-stream failures
invalidate the session without replaying command side effects.
Describe the one-retry boundary for replay-safe computerd operations,
the mandatory pre-exec push, and the rule against replaying an
ambiguous command spawn. Record the container-local durability limit
and add a patch changeset for the published behavior.
Classify outbound interception failures as transport errors alongside
the other container connection stages. A replacement container can
re-enter the full connection path when the platform is not ready
during egress setup.
The two arms of `BackendRetryMode` described different things. The
`idempotent` value named a property of the operation, while
`pre-dispatch` named the evidence the reconnect loop needs before it
replays. At the call sites it was not clear that the two were
alternatives for the same decision.

Rename the type to `BackendRetryPolicy` and its values to `always` and
`pre-dispatch`, so both name what the reconnect loop is allowed to do.
The parameter on `#runWithReconnect` and `#runShellEnvelope` becomes
`policy` to match. Behavior is unchanged: `always` replays any
transport failure, `pre-dispatch` replays only when the failure proves
no frame reached the peer, and `shell.exec` remains the sole caller
that asks for the narrower policy.
Keep the package README focused on its public surface and rely on the design documentation for reconnect details. Rewrite the changeset as a user-facing outcome and link it to the command synchronization contract.
@changeset-bot

changeset-bot Bot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0f04be

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@cloudflare/computer Patch
@cloudflare/dofs Patch
@cloudflare/computer-rpc Patch
@cloudflare/computerd Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@cloudflare/computer@103

commit: d0f04be

devin-ai-integration[bot]

This comment was marked as resolved.

Run the pre-exec push and command spawn as one reconnect attempt on
one backend handle. If that handle fails before dispatch, repeat both
steps on the replacement so it receives the workspace before the
command starts.
Persist a UUID in the container-owning Durable Object for each running
container process. Preserve it across connection changes, rotate it on
process replacement, clear it on exit, and attach it to backend
handles so process-local operations can detect a changed runtime.
Stamp container executions with the UUID of the process that accepted
them. Retry get, kill, and dispose across connection changes only when
the UUID still matches, and return EEXEC_LOST before an old execution
id can reach a replacement process table.
devin-ai-integration[bot]

This comment was marked as resolved.

Keep runtime ownership for the 1024 most recently used execution ids.
Returned execution handles retain their UUID directly, while older
by-id lookups degrade to no generation expectation instead of growing
memory for the lifetime of a busy Durable Object.
devin-ai-integration[bot]

This comment was marked as resolved.

Carry the execution runtime UUID through post-command pull and durable
retry intents. If reconnect reaches an empty replacement container,
keep sync pending instead of reporting a successful zero-entry pull
that would hide unrecoverable command writes.
devin-ai-integration[bot]

This comment was marked as resolved.

Treat a pending post-command pull as unrecoverable when its runtime UUID
no longer matches the live container. Clear that stale intent, report
it as lost, and let a later failure from the current runtime replace an
older intent so new sync work cannot be blocked forever.
devin-ai-integration[bot]

This comment was marked as resolved.

Store the latest runtime UUID for each backend and execution id in
Workspace SQLite, using the bounded LRU only as a cache. Direct get,
kill, and dispose calls now retain their process fence across Durable
Object incarnations and cache eviction.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment on lines +281 to +283
// Last known container runtime for recent backend/execution ids.
// Returned handles carry their own id; the bounded LRU supports
// direct by-id lifecycle calls without growing for the DO lifetime.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New code comment uses an abbreviation the project's writing rules forbid

A newly added comment abbreviates "durable object" as "DO" (packages/computer/src/workspace.ts:283), which the repository's prose rules explicitly forbid for comments.
Impact: The comment does not follow the project's mandatory writing style for code comments.

Rule reference

AGENTS.md requires loading .agents/skills/prose/SKILL.md when writing code comments, and that file states: "IMPORTANT! Avoid acronyms. Use the full word, e.g. "durable object" instead of "DO"". The rest of this change consistently spells out "Durable Object" (for example packages/computer/src/execution-runtime-tracker.ts:3-4), so this line is inconsistent as well as non-conforming.

Suggested change
// Last known container runtime for recent backend/execution ids.
// Returned handles carry their own id; the bounded LRU supports
// direct by-id lifecycle calls without growing for the DO lifetime.
// Last known container runtime for recent backend/execution ids.
// Returned handles carry their own id; the bounded LRU supports
// direct by-id lifecycle calls without growing for the whole
// Durable Object lifetime.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@aron-cf
aron-cf merged commit 61ab673 into main Aug 14, 2026
19 checks passed
@aron-cf
aron-cf deleted the fix-container-restart branch August 14, 2026 14:14
@github-actions github-actions Bot mentioned this pull request Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

exec's view of the workspace filesystem doesn't reflect durable state right after a container restart

1 participant