From 823e27ed061c9e78a87e7594dcd8fe2cc32a11b2 Mon Sep 17 00:00:00 2001 From: Ahmed Abushagur Date: Tue, 28 Apr 2026 00:30:44 -0700 Subject: [PATCH] fix(tarball): rewrite absolute /root symlinks after non-root extract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tarballs are built as root with absolute symlinks (e.g. ~/.local/bin/claude -> /root/.claude/local/claude). When extracted on a non-root user (Sprite, scp-style installs without sudo), tar's --transform rewrites file PATHS but not symlink TARGETS, so the symlinks land in $HOME pointing at /root/... — which doesn't exist. The agent binary appears installed but is a dangling link, and PATH lookup fails with "command not found". After the transform extract succeeds, walk $HOME for symlinks whose target starts with /root/ and rewrite them to point under $HOME. Idempotent (ln -snf), no-ops on healthy installs, cheap (one find on a fresh VM's $HOME). Bumps CLI to 1.0.24. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/cli/package.json | 2 +- packages/cli/src/shared/agent-tarball.ts | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 38f81cd17..96dda861f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "1.0.23", + "version": "1.0.24", "type": "module", "bin": { "spawn": "cli.js" diff --git a/packages/cli/src/shared/agent-tarball.ts b/packages/cli/src/shared/agent-tarball.ts index 0e9d176dd..d559f29f0 100644 --- a/packages/cli/src/shared/agent-tarball.ts +++ b/packages/cli/src/shared/agent-tarball.ts @@ -101,12 +101,29 @@ export async function tryTarballInstall( // - Non-root user: use tar --transform to remap /root/ to $HOME/ during extraction. // This avoids needing sudo entirely (Sprite VMs don't have it). // Falls back to sudo-based extraction for clouds with passwordless sudo (AWS, GCP). + // + // After non-root extraction we also have to rewrite SYMLINK TARGETS — tar's + // --transform only rewrites file names, not the absolute paths stored inside + // symlinks. Without this, the agent's binary symlinks (e.g. + // ~/.local/bin/claude -> /root/.claude/local/claude) extract as dangling + // links and `claude` shows up as "command not found" on PATH. + const fixSymlinks = [ + 'find "$HOME" -type l 2>/dev/null | while IFS= read -r _l; do', + ' _t=$(readlink "$_l" 2>/dev/null) || continue', + ' case "$_t" in', + ' /root/*) ln -snf "$HOME${_t#/root}" "$_l" 2>/dev/null ;;', + " esac", + "done", + "true", + ].join(" "); + const extractCmd = [ 'if [ "$(id -u)" = "0" ]; then', " tar xz -C /", "else", - // Try transform first (no sudo needed) — remap /root/ paths to $HOME/ - ' tar xz --transform "s|^root/|${HOME#/}/|" -C / 2>/dev/null ||', + // Try transform first (no sudo needed) — remap /root/ paths to $HOME/, + // then walk $HOME and rewrite any leftover absolute /root/ symlinks. + ` { tar xz --transform "s|^root/|\${HOME#/}/|" -C / 2>/dev/null && { ${fixSymlinks}; }; } ||`, // Fallback: sudo extract + mirror (for clouds with passwordless sudo) " sudo tar xz -C / 2>/dev/null", "fi",