From 0e893cc292ee3bfa1472df0230f555c6561242e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 4 Aug 2026 12:22:16 +0000 Subject: [PATCH] scripts: stop creating the /usr/local/bin/claude symlink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The symlink existed because the Claude sidecar hardcoded that path, and that hardcoding came from the bwrap-sandboxed architecture: the jail ro-bound /usr and could not see the installer's real target in ~/.local/bin. The sandbox is gone, and claude-manager.ts now resolves the CLI itself — $CLAUDE_BIN, then PATH, then ~/.local/bin/claude, /usr/local/bin/claude, /opt/homebrew/bin/claude. Verified before removing rather than assumed: - the only references left in the tree are the resolver's own fallback list and this step; nothing in capabilities, no systemd unit, no crontab, no ecosystem file and no shell rc mentions the path - the agent sidecar's PATH under pm2 contains ~/.local/bin ahead of /usr/local/bin, so Bun.which resolves to the installer's target and the symlink is never consulted - replaying the resolver in that exact environment with the symlink treated as absent returns the same path, so it is not load-bearing - resolveClaudeBin runs at claude-manager module scope, which ES import ordering puts before user-instance.ts reassigns process.env.HOME — so the homedir() candidate is evaluated against the real home, not the managed one The install-and-verify step above is untouched, so a failed claude-code install is still reported. Only the sudo-owned link into /usr/local/bin goes, a directory macOS does not ship at all. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/setup.sh | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index e243f704..e15a7f80 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -759,20 +759,13 @@ else if has claude; then ok "claude-code installed"; else warn "claude-code install failed"; fi fi - # The platform's Claude sidecar execs /usr/local/bin/claude (a stable, sandbox-visible path). The - # installer only drops the CLI in ~/.local/bin, so symlink it there — pointing at the ~/.local/bin - # launcher so it keeps tracking Claude's self-updates. Without this, claude chat ENOENTs on a fresh host. - CLAUDE_LOCAL="$HOME/.local/bin/claude" - if [ -e "$CLAUDE_LOCAL" ]; then - if [ "$(readlink -f /usr/local/bin/claude 2>/dev/null)" != "$(readlink -f "$CLAUDE_LOCAL")" ]; then - sudo ln -sf "$CLAUDE_LOCAL" /usr/local/bin/claude - ok "claude symlinked to /usr/local/bin/claude" - else - skip "claude symlink at /usr/local/bin/claude" - fi - else - warn "claude not found at $CLAUDE_LOCAL — /usr/local/bin/claude symlink skipped" - fi + # No /usr/local/bin/claude symlink. That existed because the sidecar hardcoded that path, which in + # turn came from the bwrap-sandboxed architecture — the jail ro-bound /usr and could not see the + # installer's real target in ~/.local/bin. The sandbox is gone and claude-manager.ts now resolves the + # CLI itself: $CLAUDE_BIN, then PATH, then ~/.local/bin/claude, /usr/local/bin/claude and + # /opt/homebrew/bin/claude. The installer above puts it in ~/.local/bin, which is both on PATH and the + # first candidate, so the symlink was satisfying a requirement that no longer exists — at the cost of + # a sudo-owned link into /usr/local/bin, a directory macOS does not even ship. # pm2 (process manager) if has pm2; then