Skip to content

fix(cloudflare/containers): bind a container's DO namespace and script metadata under one sid - #1271

Merged
sam-goodwin merged 2 commits into
alchemy-run:mainfrom
spinx:fix/container-async-binding
Aug 23, 2026
Merged

fix(cloudflare/containers): bind a container's DO namespace and script metadata under one sid#1271
sam-goodwin merged 2 commits into
alchemy-run:mainfrom
spinx:fix/container-async-binding

Conversation

@spinx

@spinx spinx commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

A Cloudflare.Container bound on an async Worker's env uploaded as a json binding holding the serialized declaration ({"_id":"Effect","op":"alchemy/EffectClass"}) instead of a durable_object_namespace, whenever the env key and the Container's logical id matched:

env: { Sandbox: Cloudflare.Container("Sandbox", { className: "Sandbox", context, dockerfile }) }

At runtime env.Sandbox is then a plain object, and @cloudflare/sandbox's getSandbox(ns, id) dies with t.idFromName is not a function.

bindContainerClass contributed the Worker's two halves in separate bind calls, and binding rows are collapsed by sid — last write wins (Diff.ts), so the containers row silently dropped the namespace row:

-yield* resource.bind`${bindingName}`({ bindings: [durableObjectNamespace] });   // sid = "Sandbox"
-yield* resource.bind`${application.LogicalId}`({ containers: [{ className }] }); // sid = "Sandbox"
+yield* resource.bind`${bindingName}`({
+  bindings: [durableObjectNamespace],
+  containers: [{ className }],
+});

Both rows describe the same env entry, so they belong under one sid. With them split, the upload carried no DO class at all (currentDoClassNameByLogicalId: {}), and appendAlchemyAndEnvBindings — which JSON-ifies any env value not already bound by name — emitted the declaration as json.

The existing async fixture escaped this by naming the Container AsyncEchoContainer while binding it as env.ECHO; it now uses Container("ECHO") so the live suite covers the colliding shape. A new offline test (AsyncContainerBinding.test.ts) pins the sid-collapsed rows for both namings.

Workaround on released versions: give the Container a logical id that differs from the env key.

spinx and others added 2 commits August 20, 2026 08:37
…t metadata under one sid

Binding rows are collapsed by `sid` (last write wins), so the two `bind`
calls in `bindContainerClass` silently dropped one another whenever the env
key and the Container's logical id matched:

    env: { Sandbox: Cloudflare.Container("Sandbox", { className: "Sandbox" }) }

The `containers` row (sid = application logical id) clobbered the
`durable_object_namespace` row (sid = env key), so the Worker uploaded the
Container declaration itself as a `json` binding
(`{"_id":"Effect","op":"alchemy/EffectClass"}`) and `getSandbox(env.Sandbox)`
died at runtime with `t.idFromName is not a function`.

Both rows describe the same env entry, so contribute them in one `bind`.
…worker

Replace the compile-only binding-collection test with real coverage in
Container.test.ts's dev/live matrix: the async fixture (whose Container
logical id equals its env key) is deployed and driven over HTTP.

- /binding asserts env.ECHO is a durable_object_namespace, not the
  Container declaration uploaded as json
- /hello asserts the request round-trips Worker -> DO -> container

Without the fix both fail: dev dies with `Durable Object namespace
AsyncEchoObject not found`, live reports
`{"_id":"Effect","op":"alchemy/EffectClass"}`.

AsyncContainer.test.ts is folded in — Container.test.ts now covers the
same stack in both dev and live.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sam-goodwin
sam-goodwin merged commit 040ce2a into alchemy-run:main Aug 23, 2026
5 checks passed
sam-goodwin added a commit that referenced this pull request Aug 23, 2026
Issue #1321 reported the #953 symptom — `getContainer(env.X)` dying with
`idFromName is not a function` — for a Container bound on an async Worker's
`env` in the shape the Containers guide documents: `className` omitted, so
the env key, the Container's logical id and the DO class share one name.
The fix landed in #1271; this pins that exact shape with a deployed fixture,
driven over HTTP in both dev and live.

Without #1271 the deployed Worker reports env.Probe as
`{"_id":"Effect","op":"alchemy/EffectClass"}`; with it, a namespace that
round-trips Worker -> DO class -> container.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants