Symptom
getWorkspace(stub) never resolves against a deployed Worker + Durable Object using CloudflareContainerBackend. Every call surfaces:
WorkspaceTransportError: Workspace backend "container-shell": shell.exec failed after 1 reconnect
retry: initial=CloudflareContainerBackend(container-shell) [stage=ws]: /ws upgrade did not arrive
within 30000ms; last=CloudflareContainerBackend(container-shell) [stage=ws]: /ws upgrade did not
arrive within 30000ms
No exec() ever runs, so nothing downstream of the connection can be exercised.
Versions
@cloudflare/computer@0.2.1
ghcr.io/cloudflare/computer-computerd-linux-x64:0.2.1
wrangler@4.123.0
Reproduction
A Worker + Durable Object wired to match examples/container/src/index.ts as closely as I could read it: the ContainerBase / withWorkspaceContainer / CloudflareContainerBackend construction, egress: { mode: "direct" }, export { WorkspaceProxy }, override fetch() { return this.backend.handleFetch(request) }, observer: createCloudflareObserver({ tracing }), and an R2Bucket(env.Bucket) mount.
wrangler.jsonc matched to examples/container/wrangler.jsonc: instance_type: "standard-2", compatibility_date: "2026-05-26", rollout_active_grace_period: 0, rollout_step_percentage: [100], observability: { traces: { enabled: true } }.
Deployed with wrangler deploy — not wrangler dev. I never reproduced or ruled this out locally; it only appears against real infrastructure. Then call any route that reaches getWorkspace(). It times out at [stage=ws] every time.
Both ends look healthy
wrangler containers list / containers info show the application reaching a healthy instance in every round.
wrangler tail on the Worker shows computerd completing round trips, paired and roughly 30 s apart — which lines up with the backend's own connectTimeoutMs retry cadence rather than any schedule of computerd's:
GET http://computer.internal/health - Ok
GET http://computer.internal/ws - Ok
/health clearly works end to end. The anomaly is that /ws reaches the Worker and logs as Ok while the backend's #pendingUpgrade promise (packages/computer/src/backends/container/cloudflare-container.ts) never resolves.
A 200 (or any non-101) response to that GET would produce exactly this symptom and would still print as Ok in wrangler tail's compact line. I could not distinguish that from a 101 arriving too late without visibility into what handleFetch() actually returned — that is the single most useful thing someone with Cloudflare-side access could check.
Variables eliminated
Four independently deployed Application IDs, three fresh Durable Object identities per round.
| Variable |
Tried |
Result |
| Stale DO state |
multiple fresh DO ids per round |
no change |
| Stale platform state |
full wrangler delete + containers delete + fresh deploy, new Application ID |
no change |
| Patience |
connectTimeoutMs 30 s → 45 s (~90 s wall clock across the built-in reconnect) |
no change |
egress.mode |
"direct" (matched from the first draft) |
no change |
instance_type |
standard-1 and standard-2 |
no change |
compatibility_date |
2026-08-01 and 2026-05-26 |
no change |
| Rollout policy |
absent, and 0 / [100] |
no change |
observability shape |
{ enabled: true } and { traces: { enabled: true } } |
no change |
| R2 mount |
absent and present |
no change |
The last two were matched for a measurement rather than an argument — reading the source, createCloudflareObserver is a pass-through around tracing.enterSpan() with no network or async gating, and the mount only touches ws.fs, structurally separate from the /ws control channel. Neither was a plausible cause; both were tested anyway.
One secondary observation, offered as data rather than as the explanation: standard-2 scheduling looked less reliable than standard-1 on this account/region — longer to reach a healthy instance, and one of two max_instances slots more often stuck starting. But a request landing on a confirmed-healthy instance still failed identically.
What I could not rule out
Anything needing Cloudflare-side visibility: the actual status and body handleFetch() returned on the /ws GET that timed out, and whether ctx.container.interceptOutboundHttp behaves differently under real Firecracker-backed Containers than in the environment the SDK's own tests exercise. I note that the FUSE-deadlock regression test (packages/computerd/src/exec/runner.fuse.test.ts) runs against a privileged local Docker container, never a deployed Cloudflare Container — so a deployed-only transport difference would not be caught by CI.
I understand the package is explicitly preview and not for production; this is filed as a data point from a real deployment, not as a support request.
Symptom
getWorkspace(stub)never resolves against a deployed Worker + Durable Object usingCloudflareContainerBackend. Every call surfaces:No
exec()ever runs, so nothing downstream of the connection can be exercised.Versions
@cloudflare/computer@0.2.1ghcr.io/cloudflare/computer-computerd-linux-x64:0.2.1wrangler@4.123.0Reproduction
A Worker + Durable Object wired to match
examples/container/src/index.tsas closely as I could read it: theContainerBase/withWorkspaceContainer/CloudflareContainerBackendconstruction,egress: { mode: "direct" },export { WorkspaceProxy },override fetch() { return this.backend.handleFetch(request) },observer: createCloudflareObserver({ tracing }), and anR2Bucket(env.Bucket)mount.wrangler.jsoncmatched toexamples/container/wrangler.jsonc:instance_type: "standard-2",compatibility_date: "2026-05-26",rollout_active_grace_period: 0,rollout_step_percentage: [100],observability: { traces: { enabled: true } }.Deployed with
wrangler deploy— notwrangler dev. I never reproduced or ruled this out locally; it only appears against real infrastructure. Then call any route that reachesgetWorkspace(). It times out at[stage=ws]every time.Both ends look healthy
wrangler containers list/containers infoshow the application reaching ahealthyinstance in every round.wrangler tailon the Worker shows computerd completing round trips, paired and roughly 30 s apart — which lines up with the backend's ownconnectTimeoutMsretry cadence rather than any schedule of computerd's:/healthclearly works end to end. The anomaly is that/wsreaches the Worker and logs asOkwhile the backend's#pendingUpgradepromise (packages/computer/src/backends/container/cloudflare-container.ts) never resolves.A
200(or any non-101) response to that GET would produce exactly this symptom and would still print asOkinwrangler tail's compact line. I could not distinguish that from a101arriving too late without visibility into whathandleFetch()actually returned — that is the single most useful thing someone with Cloudflare-side access could check.Variables eliminated
Four independently deployed Application IDs, three fresh Durable Object identities per round.
wrangler delete+containers delete+ fresh deploy, new Application IDconnectTimeoutMs30 s → 45 s (~90 s wall clock across the built-in reconnect)egress.mode"direct"(matched from the first draft)instance_typestandard-1andstandard-2compatibility_date2026-08-01and2026-05-260/[100]observabilityshape{ enabled: true }and{ traces: { enabled: true } }The last two were matched for a measurement rather than an argument — reading the source,
createCloudflareObserveris a pass-through aroundtracing.enterSpan()with no network or async gating, and the mount only touchesws.fs, structurally separate from the/wscontrol channel. Neither was a plausible cause; both were tested anyway.One secondary observation, offered as data rather than as the explanation:
standard-2scheduling looked less reliable thanstandard-1on this account/region — longer to reach a healthy instance, and one of twomax_instancesslots more often stuckstarting. But a request landing on a confirmed-healthy instance still failed identically.What I could not rule out
Anything needing Cloudflare-side visibility: the actual status and body
handleFetch()returned on the/wsGET that timed out, and whetherctx.container.interceptOutboundHttpbehaves differently under real Firecracker-backed Containers than in the environment the SDK's own tests exercise. I note that the FUSE-deadlock regression test (packages/computerd/src/exec/runner.fuse.test.ts) runs against a privileged local Docker container, never a deployed Cloudflare Container — so a deployed-only transport difference would not be caught by CI.I understand the package is explicitly preview and not for production; this is filed as a data point from a real deployment, not as a support request.