From 4c72f02a06525f62ee65b8668ed7ae03b1f77b9f Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 11 May 2026 14:17:07 -0700 Subject: [PATCH] Address Greptile review on sandbox install fixes Two findings from the Greptile pass on PAPA-316: 1. sandbox-install-command: the export PATH and PAPERCLIP_NPM_BOOTSTRAPPED=1 were separated from the curl && tar && rm chain by ; so the bootstrap marker would be set even if the tarball download or extraction failed, leading to a misleading "npm: command not found" error on the install step instead of surfacing the original curl/tar failure. Switch those delimiters to && so the marker is only set after a successful bootstrap. 2. opencode-local: $HOME/.local/bin is on the default PATH on the exe.dev image but not universally on bare Debian/Ubuntu non-login sh -c shells. Prefer /usr/local/bin (always on the default PATH on Linux) when running as root or with passwordless sudo, and only fall back to $HOME/.local/bin when neither is available. Mirrors the sudo-aware pattern that buildSandboxNpmInstallCommand already uses. --- .../adapter-utils/src/sandbox-install-command.ts | 4 ++-- packages/adapters/opencode-local/src/index.ts | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/adapter-utils/src/sandbox-install-command.ts b/packages/adapter-utils/src/sandbox-install-command.ts index 33cf92da909..d8ef0767d25 100644 --- a/packages/adapter-utils/src/sandbox-install-command.ts +++ b/packages/adapter-utils/src/sandbox-install-command.ts @@ -24,8 +24,8 @@ const ENSURE_NPM_PREAMBLE = 'mkdir -p "$HOME/.local"; ' + 'curl -fsSL "https://nodejs.org/dist/${NODE_VERSION}/${NODE_TARBALL}" -o "/tmp/${NODE_TARBALL}" && ' + 'tar -xJf "/tmp/${NODE_TARBALL}" -C "$HOME/.local" --strip-components=1 && ' + - 'rm -f "/tmp/${NODE_TARBALL}"; ' + - 'export PATH="$HOME/.local/bin:$PATH"; ' + + 'rm -f "/tmp/${NODE_TARBALL}" && ' + + 'export PATH="$HOME/.local/bin:$PATH" && ' + "PAPERCLIP_NPM_BOOTSTRAPPED=1; " + "fi;"; diff --git a/packages/adapters/opencode-local/src/index.ts b/packages/adapters/opencode-local/src/index.ts index 647dc61a17f..6f36e378e6f 100644 --- a/packages/adapters/opencode-local/src/index.ts +++ b/packages/adapters/opencode-local/src/index.ts @@ -12,8 +12,11 @@ export const label = "OpenCode (local)"; // `$HOME/.opencode/bin` and tries to add it to PATH via `~/.bashrc`. That // rc-file path is only sourced by interactive/login shells, so non-login // `sh -c` probe invocations (used by the runtime PATH check) cannot find the -// binary. We fix that by symlinking the installed binary into -// `$HOME/.local/bin`, which is on the default PATH on every distro we target. +// binary. We fix that by symlinking the installed binary into a directory on +// the non-login `sh -c` PATH: prefer `/usr/local/bin` (universally on the +// default PATH on Linux distros) when root or passwordless sudo is available, +// otherwise fall back to `$HOME/.local/bin` (which is on the default PATH on +// the exe.dev sandbox image and most modern home-managed Linux images). // // Security tradeoff: this is `curl | bash` without a SHA-256 verification of // the install script. We accept this because: @@ -30,9 +33,15 @@ export const label = "OpenCode (local)"; // a versioned tarball + verifying the digest before exec. export const SANDBOX_INSTALL_COMMAND = 'curl -fsSL https://opencode.ai/install | bash && ' + - 'mkdir -p "$HOME/.local/bin" && ' + 'if [ -x "$HOME/.opencode/bin/opencode" ]; then ' + + 'if [ "$(id -u)" -eq 0 ]; then ' + + 'ln -sf "$HOME/.opencode/bin/opencode" /usr/local/bin/opencode; ' + + 'elif command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then ' + + 'sudo ln -sf "$HOME/.opencode/bin/opencode" /usr/local/bin/opencode; ' + + 'else ' + + 'mkdir -p "$HOME/.local/bin" && ' + 'ln -sf "$HOME/.opencode/bin/opencode" "$HOME/.local/bin/opencode"; ' + + 'fi; ' + 'fi'; export const DEFAULT_OPENCODE_LOCAL_MODEL = "openai/gpt-5.2-codex";