Skip to content

computerd: let the caller choose the exec shell - #139

Merged
aron-cf merged 1 commit into
mainfrom
shell-config
Sep 11, 2026
Merged

computerd: let the caller choose the exec shell#139
aron-cf merged 1 commit into
mainfrom
shell-config

Conversation

@aron-cf

@aron-cf aron-cf commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Every command from exec runs through a hardcoded /bin/sh. On a Debian-family image that is dash, where the syntax a caller reaches for when dash is not enough fails as a parse error rather than as a missing feature, so the whole command stops instead of carrying on.

This matters most for PIPESTATUS. A pipeline reports the exit status of its last stage, so a caller that filters output through another command loses the real result. Redacting a credential out of a push is the common case, and it quietly reports success when the push failed:

git push "$URL" main 2>&1 | sed -E 's#//[^@]*@#//***@#'
echo $?   # 0, even though the push failed

The usual way to recover the real status is ${PIPESTATUS[0]}, which bash has and dash does not. Under dash it is worse than the problem it was reached for:

What the caller writes What dash does
${PIPESTATUS[@]} Bad substitution, and the rest of the command never runs
a=(1 2 3) syntax error
[[ 1 == 1 ]] [[: not found
<(some command) syntax error

There was no way to change this. The runner had no option for it, SHELL is deliberately left out of the environment passed to children, and the only workaround was repointing /bin/sh inside the image, which changes how echo treats backslashes for every other script there and is impossible with a prebuilt image.

The runner now takes the interpreter as an option and the daemon reads the same value from EXEC_SHELL. Both default to /bin/sh, so nothing changes for existing callers unless they ask for something else. The path must be absolute, and a relative one is refused when the runner is built rather than failing later on every command.

const runner = new Runner({ db, shell: "/usr/bin/bash" });

new Runner({ db, shell: "bash" });
// Error: shell must be an absolute path; got "bash"

Picking an interpreter is the caller's choice and nothing about pipelines changes on its own. Turning on pipefail by default would alter the exit status of existing callers, because a grep that matches nothing or a diff that finds a difference would start reporting failure. Callers who want that can put set -o pipefail at the top of their command on either interpreter.

To see it work, run the daemon as EXEC_SHELL=/usr/bin/bash computerd and send this through exec:

false | true; printf '[%s]' "${PIPESTATUS[0]}"; printf "after"
# [1]after   — on a dash image without EXEC_SHELL, nothing prints at all

Four tests cover it: the default is still /bin/sh, a chosen interpreter is the one that runs, PIPESTATUS resolves under it instead of stopping the command, and a relative path is refused. The two that need bash skip themselves when it is missing, so the suite still passes without it. The default test is what shows existing callers are unaffected, and it passes both before and after the change.

The computerd readme now documents EXEC_SHELL alongside the other environment variables it accepts.


Devin Review

Every exec command runs through a hardcoded /bin/sh. On a Debian-family
image that is dash, where bash-only syntax is a parse error rather than
a missing feature, so the whole command aborts instead of degrading.
That matters most for the PIPESTATUS array: a caller that filters a
command's output, such as piping git through sed to redact a credential
from the URL, gets the pipeline's last exit status and reads a failed
push as a success. PIPESTATUS is how the real status is recovered, and
under dash reaching for it is worse than the problem it was reached for.

Add an optional shell to the runner options, defaulting to /bin/sh, and
read the same value from EXEC_SHELL so a deployed daemon can set it
without a rebuild. Existing callers are unaffected. The previous
workaround was to repoint /bin/sh inside the image, which changes echo
semantics for every other script there and is unavailable when the image
is prebuilt.
@changeset-bot

changeset-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2bc75d1

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

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

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

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@pkg-pr-new

pkg-pr-new Bot commented Sep 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 2bc75d1

@aron-cf
aron-cf merged commit 5f310b6 into main Sep 11, 2026
20 checks passed
@aron-cf
aron-cf deleted the shell-config branch September 11, 2026 15:47
@github-actions github-actions Bot mentioned this pull request Sep 11, 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.

1 participant