Skip to content

container backend: every exec runs the literal string undefined because the pinned computerd predates the command→source rename #62

Description

@3x3xX3N0N

Describe the bug

Every runtime.exec call against the container backend runs the literal string undefined instead of the caller's command, because the computerd image the example Dockerfiles pin predates the exec wire-field rename.

04b7f34 ("rpc, computer: rename the shell exec field to source", 2026-08-03) renamed ShellRPC.exec's field from command to source. The published image is 0.1.0-alpha.1, tagged by 48aa155 ("chore: tag v0.1.0-alpha.1") on 2026-07-30 β€” three days earlier. The binary inside it still reads input.command.

The host now sends source:

  • packages/computer/src/shell.ts:130 β€” this.#shell.exec({ source, id, cwd, timeoutMs, env, stdin })
  • packages/rpc/src/server.ts:251 β€” return this.runner.exec(input.source, { ... })

The pinned daemon reads input.command, gets undefined, and interpolates it into the shell string at packages/computerd/src/exec/runner.ts:144-145:

const wrapped = cwd !== undefined ? `cd ${shellQuote(cwd)} && ${command}` : command;
const child = spawn("/bin/sh", ["-c", wrapped], { ... });

so /bin/sh receives the four characters undefined as its command:

{"status":"failed","exitCode":127,"stdout":"","stderr":"/bin/sh: 1: undefined: not found\n",
 "sync":{"status":"complete","applied":0,"skipped":[]}}

Note the failure is silent in shape: sync succeeds, the RPC round trip succeeds, and the error surfaces only as a shell "command not found" for a command the caller never wrote.

The stale pin appears in six places:

  • examples/container/Dockerfile:17
  • examples/think/Dockerfile:12
  • examples/tutorial/Dockerfile:11
  • examples/think-compare-runtimes/Dockerfile.workspace:15
  • docs/README.md:66
  • packages/computer-computerd-linux-x64/README.md:23

Related, and possibly a separate bug: examples/think-compare-runtimes has "predev": "npm run build:computerd", which builds the daemon from source β€” but packages/computerd/scripts/build-docker.mjs:28 tags it cloudflare/computer-computerd-linux-x64 (no registry prefix) while Dockerfile.workspace:15 consumes ghcr.io/cloudflare/.... The example builds a correct binary and then does not use it.

Prior art: #52's repro notes the reporter deployed examples/container "unmodified except for … computerd tag bumped to 0.1.1", i.e. an independent reporter hit this and worked around it without filing. #59 documents a sibling wire-contract break from the same refactor family, and quotes docs/08:127-129 on the interface having no version negotiation ("hard wire breaks") β€” which is the policy this violates.

Expected behavior

runtime.exec(source, ...) against the container backend runs source. Either the examples pin an image whose computerd reads the same field name the host sends, or the wire mismatch is detected and reported as a protocol error rather than degrading into a shell "command not found".

Steps to reproduce

  1. npm install at the repo root, then cd examples/container.
  2. npx wrangler dev (Linux or WSL β€” containers are unsupported on Windows).
  3. Write a file, then exec:
curl -X PUT --data-binary "hello" http://127.0.0.1:8787/c/demo/file/workspace/hello.txt
curl -X POST http://127.0.0.1:8787/c/demo/exec \
  -H 'content-type: application/json' -d '{"command":"echo EXEC_OK"}'
  1. Observed: {"status":"failed","stderr":"/bin/sh: 1: undefined: not found\n", ...}. Expected: EXEC_OK on stdout.

The write/read path succeeds, so this is specific to exec.

Confirming the cause: building the daemon from source and making the example consume it makes the same request succeed:

npm run build:docker --workspace @cloudflare/computerd
docker tag cloudflare/computer-computerd-linux-x64:0.1.0-alpha.1 \
           ghcr.io/cloudflare/computer-computerd-linux-x64:0.1.0-alpha.1
docker rmi -f $(docker images -q cloudflare-dev/containerexample)   # else wrangler reuses the cached image

Re-running step 3 then returns {"status":"completed","exitCode":0,"stdout":"EXEC_OK\n"}.

The docker rmi matters: wrangler derives its image tag from the Dockerfile text, so retagging the base alone does not invalidate its cache and the old image is silently reused.

Proposed fix

Publish a computerd image built after 04b7f34 and bump the six pins, or move the examples onto the locally built image. .github/changeset-version.mjs:17 (added in PR #49) already rewrites these pins during the Version Packages PR, so the release machinery exists β€” it just has not run since the rename, and the currently published tag is broken for every example that uses the container backend.

Worth considering separately: packages/rpc could reject an exec payload with no source field instead of passing undefined through to spawn, so a future wire break surfaces as a protocol error rather than a shell error.

Environment

  • cloudflare/computer at 76d9e75 (current main)
  • Ubuntu 24.04 under WSL2, Docker 29.1.3, wrangler 4.115.0, Node 22.22.1
  • examples/container, container backend via computerd 0.1.0-alpha.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions