Skip to content

sandbox download fails on busybox-based images: ssh probe uses GNU-only 'realpath -e' #4909

Open

Description

@addozhang

User Story

As a user running agents in Alpine/busybox-based images (including the official ghcr.io/anomalyco/opencode:latest example image), I want openshell sandbox download to retrieve my results, so that the documented upload → run → download workflow works on any standard base image.

Problem Statement

openshell sandbox download always fails on images where realpath is busybox:

Error: × failed to resolve sandbox source path '/sandbox/probe.txt'
  ╰─▶ ssh probe exited with status exit status: 1

Upload and sandbox exec are unaffected; only download breaks.

Impact / Why This Matters

The official OpenCode example image (ghcr.io/anomalyco/opencode:latest, Alpine-based) is affected, so the documented agent workflow (upload a project, run the agent, download results — e.g. the "Run Pi with OpenRouter" tutorial) is broken on that image unless the user knows to install GNU coreutils.

Acceptance Criteria

  • sandbox download works on busybox-based images, or
  • the image requirements documentation explicitly states GNU coreutils is required.

Reproduction Steps

OpenShell v0.1.5, docker compute driver:

openshell sandbox create --name repro-alpine --from alpine:3
echo hello > /tmp/probe.txt
openshell sandbox upload repro-alpine /tmp/probe.txt /sandbox/probe.txt    # works
openshell sandbox download repro-alpine /sandbox/probe.txt /tmp/out.txt
# Error: failed to resolve sandbox source path '/sandbox/probe.txt'
#   ╰─▶ ssh probe exited with status exit status: 1

Control: identical steps on an Ubuntu 24.04-based image succeed.

Root-cause analysis:

  • The CLI's ssh probe (extracted via strings from the v0.1.5 openshell binary) runs: pwd -P && realpath -e -- <path>
  • busybox realpath has no -e flag and treats it (and --) as a filename:
/sandbox # realpath -e -- /sandbox/probe.txt
realpath: -e: No such file or directory
realpath: --: No such file or directory
/sandbox/probe.txt
/sandbox # echo $?
1
  • GNU coreutils realpath -e exits 0 for the same path.
  • Fix verification: adding apk add coreutils to the image makes download succeed with no other changes — the probe is the only blocker.

Suggested UX (if applicable)

Use a POSIX-compatible probe, e.g. test -e <path> && realpath -- <path>, or detect a usage error from realpath -e and fall back. Alternatively, document the GNU coreutils requirement for sandbox images.

Environment

  • OpenShell v0.1.5 (Homebrew install, macOS Apple Silicon 26.6.2)
  • Docker compute driver; Docker provider: OrbStack 2.2.4 (Docker Engine 29.8.2, API 1.56). Note: OrbStack is not in the support matrix, but this bug is client-side parsing of probe output and depends only on the sandbox image's realpath, not on the Docker provider.
  • Images tested: alpine:3 (fails), Ubuntu 24.04-based custom image (works), alpine:3 + coreutils (works), ghcr.io/anomalyco/opencode:latest (fails)

Logs

$ openshell sandbox upload repro-alpine /tmp/probe.txt /sandbox/probe.txt
✓ Upload complete

$ openshell sandbox download repro-alpine /sandbox/probe.txt /tmp/out.txt
Error: × failed to resolve sandbox source path '/sandbox/probe.txt'
  ╰─▶ ssh probe exited with status exit status: 1

Activity

  1. O96a commented on Oct 25, 2026

    @O96a

    The probe shouldn't canonicalize at all — realpath -e is doing an existence check and a path resolution, and only the existence check matters for a download.

    [ -e "$path" ] || { echo "no such path: $path" >&2; exit 1; }

    Pass the path through verbatim; canonicalization only earns its keep when you're comparing two paths for equality, which a download probe isn't. If resolution genuinely is needed, busybox ships readlink -f, and the pure-POSIX fallback is cd "$(dirname "$p")" && pwd -P plus basename — both work on Alpine and on GNU. Guard with command -v realpath >/dev/null so a missing applet degrades to the fallback instead of failing the probe.

    Worth grepping the rest of the ssh probe path for other -e/--canonicalize usage before shipping the fix, otherwise the next busybox image hits the same wall one step later.

  2. addozhang commented on Oct 25, 2026

    @addozhang
    Author

    Thanks — agreed that the probe shouldn't canonicalize at all; the existence check is the only thing the download decision needs.

    Following your suggestion, I grepped the v0.1.5 CLI binary for every command the ssh path runs inside the sandbox, and tested each on Alpine (busybox):

    Command (from strings) Purpose busybox result
    pwd -P workspace discovery OK
    if [ -d <dst> ]; then printf dir; elif [ -e <dst> ]; then printf file; else printf missing; fi upload destination probe OK (pure POSIX)
    pwd -P && realpath -e -- <path> download source probe exit 1 (this bug)
    tar cf - -C <dir> -- <entry> download archive creation OK — exit 0, valid archive, host-side extraction roundtrip verified

    So there is no second busybox wall behind this one on the download path: with the probe fixed, the remaining tar cf - -C … -- … invocation works as-is on Alpine.

    (Upload is unaffected for a different reason: it goes through the openshell-upload-v1 supervisor protocol, which runs no commands inside the sandbox over ssh.)

    One nuance on the suggested command -v realpath guard: on Alpine, realpath exists (busybox applet) but rejects -e, treating it as a filename — so presence-detection doesn't discriminate here; the failure mode is flag compatibility, not a missing applet. For reference, verified on the same Alpine sandbox:

    • readlink -f -- <path> → exit 0 (and GNU supports -f too), so it's a drop-in replacement if resolution is ever needed;
    • your pure-POSIX fallback (cd "$(dirname …)" && pwd -P + basename) also works on busybox.

    So the minimal robust fix is exactly your [ -e "$path" ] existence check; readlink -f is the compatible resolver if one is ever required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions