From bd7d65778895faa4a19d88681ce5c4088e153800 Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Tue, 14 Jul 2026 12:10:44 -0400 Subject: [PATCH 1/3] fix(preview): make the worktree preview launcher work from a linked worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "studio (worktree)" launch helper (.claude/preview-worktree.sh) failed to start when run from a linked worktree: - node_modules/.env.local were symlinked from $root (CLAUDE_PROJECT_DIR), which is the worktree itself — so the link pointed a worktree at its own node_modules and pnpm hit ELOOP. Derive the real main checkout from `git rev-parse --git-common-dir` instead. - The launcher can inherit an older default Node than pnpm 11 requires (>=22.13); prepend the repo's pinned .nvmrc Node (or newest installed >=22) to PATH when the current node is too old. - pnpm's pre-run deps check tried to reinstall/purge the symlinked node_modules (workspace-state mismatch), which would mutate the shared main checkout; skip it with --config.verify-deps-before-run=false. Co-Authored-By: Claude Opus 4.8 --- .claude/preview-worktree.sh | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/.claude/preview-worktree.sh b/.claude/preview-worktree.sh index 667782552..6b8edaada 100755 --- a/.claude/preview-worktree.sh +++ b/.claude/preview-worktree.sh @@ -17,6 +17,12 @@ set -e root="${CLAUDE_PROJECT_DIR:-$PWD}" cd "$root" +# The main checkout is the source of the shared node_modules / .env.local. CLAUDE_PROJECT_DIR is +# the *current* checkout, which is the worktree itself when we're launched from one — so derive +# the main checkout from git rather than assuming $root is it. (Assuming $root pointed the +# symlinks below at a worktree's own node_modules → a self-referential link → ELOOP on start.) +main_checkout="$(cd "$(dirname "$(git rev-parse --git-common-dir)")" && pwd)" + # Read the target worktree from .claude/preview-cwd (repo-relative). `read` trims surrounding # whitespace and tolerates a missing trailing newline; fall back to the main checkout when the # file is absent or empty. @@ -35,13 +41,36 @@ fi # Guard with both -e and -L so an existing real dir/file AND a dangling symlink are left alone # (a broken symlink passes -e but `ln -s` over it would fail with "File exists"). if [ ! -e node_modules ] && [ ! -L node_modules ]; then - ln -s "$root/node_modules" node_modules + ln -s "$main_checkout/node_modules" node_modules echo "[preview] Symlinked node_modules from the main checkout." >&2 fi -if [ ! -e .env.local ] && [ ! -L .env.local ] && [ -f "$root/.env.local" ]; then - ln -s "$root/.env.local" .env.local +if [ ! -e .env.local ] && [ ! -L .env.local ] && [ -f "$main_checkout/.env.local" ]; then + ln -s "$main_checkout/.env.local" .env.local echo "[preview] Symlinked .env.local from the main checkout." >&2 fi +# The preview launcher may inherit an older default Node than pnpm 11 needs (>=22.13). Prefer the +# repo's pinned .nvmrc version; otherwise fall back to the newest installed nvm node >=22. +current_major="$(node -v 2>/dev/null | sed 's/^v//; s/\..*//')" +if [ -z "$current_major" ] || [ "$current_major" -lt 22 ]; then + want="" + [ -f "$root/.nvmrc" ] && want="$(tr -d '[:space:]' < "$root/.nvmrc")" + if [ -n "$want" ] && [ -d "$HOME/.nvm/versions/node/v${want}/bin" ]; then + PATH="$HOME/.nvm/versions/node/v${want}/bin:$PATH" + elif [ -d "$HOME/.nvm/versions/node" ]; then + for d in $(ls -1 "$HOME/.nvm/versions/node" | sort -Vr); do + if [ "$(echo "$d" | sed 's/^v//; s/\..*//')" -ge 22 ]; then + PATH="$HOME/.nvm/versions/node/$d/bin:$PATH" + break + fi + done + fi + export PATH + echo "[preview] Adjusted PATH to Node $(node -v 2>/dev/null) for pnpm." >&2 +fi + echo "[preview] Serving $(pwd) on port 5173" >&2 -exec pnpm run dev +# node_modules is symlinked from the main checkout; skip pnpm's pre-run deps check so it can't +# try to reinstall/purge it (the workspace state won't match, and a purge would mutate the +# shared checkout). We just want to run the dev script against the already-installed modules. +exec pnpm --config.verify-deps-before-run=false run dev From 5d7737d2aa6da238c534f001999226a781dfc227 Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Tue, 14 Jul 2026 12:25:39 -0400 Subject: [PATCH 2/3] fix(preview): portable node-version sort + strip leading v from .nvmrc Addresses review feedback on #1503: - Replace `sort -Vr` (a GNU / newer-BSD extension that stock `sort` on older macOS lacks) with a portable numeric sort, so the newest-installed-Node fallback can't crash under `set -e`. - Strip a leading `v` from the parsed `.nvmrc` value (`${want#v}`), so a `vX.Y.Z`-style pin resolves to the correct nvm directory. Co-Authored-By: Claude Opus 4.8 --- .claude/preview-worktree.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.claude/preview-worktree.sh b/.claude/preview-worktree.sh index 6b8edaada..d4662b3df 100755 --- a/.claude/preview-worktree.sh +++ b/.claude/preview-worktree.sh @@ -55,12 +55,15 @@ current_major="$(node -v 2>/dev/null | sed 's/^v//; s/\..*//')" if [ -z "$current_major" ] || [ "$current_major" -lt 22 ]; then want="" [ -f "$root/.nvmrc" ] && want="$(tr -d '[:space:]' < "$root/.nvmrc")" + want="${want#v}" # .nvmrc may write the version with or without a leading `v`. if [ -n "$want" ] && [ -d "$HOME/.nvm/versions/node/v${want}/bin" ]; then PATH="$HOME/.nvm/versions/node/v${want}/bin:$PATH" elif [ -d "$HOME/.nvm/versions/node" ]; then - for d in $(ls -1 "$HOME/.nvm/versions/node" | sort -Vr); do - if [ "$(echo "$d" | sed 's/^v//; s/\..*//')" -ge 22 ]; then - PATH="$HOME/.nvm/versions/node/$d/bin:$PATH" + # Newest installed >=22. Portable numeric sort (descending) instead of `sort -V`, which is a + # GNU/newer-BSD extension missing from older `sort` (e.g. stock macOS). + for v in $(ls -1 "$HOME/.nvm/versions/node" | sed 's/^v//' | sort -t. -k1,1rn -k2,2rn -k3,3rn); do + if [ "${v%%.*}" -ge 22 ]; then + PATH="$HOME/.nvm/versions/node/v$v/bin:$PATH" break fi done From baf5d61f682513d005afc49c405c6df862826258 Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Tue, 14 Jul 2026 14:08:22 -0400 Subject: [PATCH 3/3] fix(preview): only report a Node PATH adjustment when one actually happened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review nit on #1503: the "Adjusted PATH to Node …" message printed unconditionally inside the <22 block, even when neither the .nvmrc dir nor the nvm fallback matched — falsely claiming success in the one case an operator most needs accurate output. Track whether PATH actually changed (`adjusted`) and gate the message on it, warning clearly otherwise. Co-Authored-By: Claude Opus 4.8 --- .claude/preview-worktree.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.claude/preview-worktree.sh b/.claude/preview-worktree.sh index d4662b3df..db3b7bd36 100755 --- a/.claude/preview-worktree.sh +++ b/.claude/preview-worktree.sh @@ -56,20 +56,28 @@ if [ -z "$current_major" ] || [ "$current_major" -lt 22 ]; then want="" [ -f "$root/.nvmrc" ] && want="$(tr -d '[:space:]' < "$root/.nvmrc")" want="${want#v}" # .nvmrc may write the version with or without a leading `v`. + adjusted="" if [ -n "$want" ] && [ -d "$HOME/.nvm/versions/node/v${want}/bin" ]; then PATH="$HOME/.nvm/versions/node/v${want}/bin:$PATH" + adjusted=1 elif [ -d "$HOME/.nvm/versions/node" ]; then # Newest installed >=22. Portable numeric sort (descending) instead of `sort -V`, which is a # GNU/newer-BSD extension missing from older `sort` (e.g. stock macOS). for v in $(ls -1 "$HOME/.nvm/versions/node" | sed 's/^v//' | sort -t. -k1,1rn -k2,2rn -k3,3rn); do if [ "${v%%.*}" -ge 22 ]; then PATH="$HOME/.nvm/versions/node/v$v/bin:$PATH" + adjusted=1 break fi done fi - export PATH - echo "[preview] Adjusted PATH to Node $(node -v 2>/dev/null) for pnpm." >&2 + # Only claim an adjustment when PATH actually changed; otherwise warn (pnpm will likely fail). + if [ -n "$adjusted" ]; then + export PATH + echo "[preview] Adjusted PATH to Node $(node -v 2>/dev/null) for pnpm." >&2 + else + echo "[preview] Warning: Node $(node -v 2>/dev/null) is older than pnpm 11 needs (>=22.13) and no nvm Node >=22 was found — pnpm may fail." >&2 + fi fi echo "[preview] Serving $(pwd) on port 5173" >&2