Skip to content

Cloudflare.Container shape methods (e.g. exec) are never registered at runtime — only fetch/getTcpPort work #656

Description

@LeonardoTrapani

Summary

Declaring custom methods on a Cloudflare.Container shape and calling them from the bound DO throws TypeError: <method> is not a function. Only fetch/getTcpPort are ever wired. The container tutorial shows agent.exec("echo hi") as a working RPC call, but the container runtime never registers shape methods.

Repro

export class Sandbox extends Cloudflare.Container<Sandbox, {
  exec: (cmd: string) => Effect.Effect<{ stdout: string; exitCode: number }>;
}>()("Sandbox", { main: import.meta.filename }) {}

// Sandbox.runtime.ts
export default Sandbox.make(Effect.sync(() => Sandbox.of({
  exec: (cmd) => /* ... */,
  fetch: Effect.succeed(HttpServerResponse.text("ok")),
})));

// inside the DO
const sandbox = yield* Cloudflare.Container.bind(Sandbox);
const container = yield* Cloudflare.start(sandbox);
yield* container.exec("echo hi"); // TypeError: container.exec is not a function

Confirmed on 2.0.0-beta.57. The live started handle has keys running, destroy, signal, getTcpPort, setInactivityTimeout, interceptOutboundHttp, interceptAllOutboundHttp, monitor, start, fetch — no exec.

Root cause

Platform hands the full shape to serve (Platform.ts:341):

runtimeContext.serve?.(impl.fetch, { shape: impl })

The Worker runtime captures it (WorkerRuntimeContext.ts:94if (options?.shape) userShape = options.shape) and DurableObjectBridge proxies the calls over DO RPC, so Worker/DO shape methods work. The Container runtime's serve takes only the handler and drops { shape } (Cloudflare/Container/Container.ts:243):

const serve = <Req = never>(handler: HttpEffect<Req>) => /* serves only impl.fetch */

bindContainer returns lifecycle methods cast satisfies Container as Shape (ContainerBinding.ts:72), and start returns { ...container, getTcpPort, fetch } (StartContainer.ts:82) — so no shape method is ever present on the handle. The as Shape cast is why container.exec(...) type-checks but is undefined at runtime.

Note

The tutorial (tutorial/cloudflare/containers.mdx) presents agent.exec("echo hi") returning { stdout, exitCode } as if RPC works. Either containers should wire shape methods (an HTTP/RPC bridge over getTcpPort, mirroring the Worker/DO path), or the docs should show only getTcpPort(port).fetch(...).


I'd love to work on a fix or an implementation if I get an ok and a steer on the intended plan.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions