fix(install): wb.upgrade brick — symlink corruption + zsh $path clobber#17
Merged
Merged
Conversation
Two related bugs surfaced during the v1.1.0 real-LLM smoke against
wb-gitlore. Together they made every wb.upgrade invocation a no-op
that flagged the workbench as "dirty" and forwarded into a broken
worktree path.
1. install.zsh:142 — `cat > "$BIN_DIR/update.wb"` followed the legacy
symlink that older install.zsh versions created (`~/.local/bin/update.wb
-> $SCRIPT_DIR/update-workbench/update.zsh`). The heredoc was written
into the *real* update.zsh, replacing the 244-line script with a
3-line self-referential forwarder pointing at a deleted worktree.
Fix: `rm -f "$DEPRECATED_SHIM"` severs any legacy symlink before
writing the shim.
2. update-workbench/update.zsh:146,165,175 — `while IFS= read -r path`
clobbered zsh's lowercase `$path` special parameter, which is a tied
array mapped to `$PATH`. First iteration replaced PATH with the
loop value (e.g. "CLAUDE.md"). Every subsequent `git diff --quiet`
in the dirty-check loop exited 127 ("command not found"), so every
template-owned path was falsely flagged as dirty and wb.upgrade
bailed with exit 2.
Fix: rename the loop variable from `path` to `tpl_path` in all three
loops (dirty check, dry-run diff, apply).
Both bugs are reproducible-on-macOS-zsh and were caught by manual
smoke against a real workbench instance. Added two regression tests:
- tests/test-install-update-wb-symlink-safe.zsh — sets up a fake HOME
with the legacy symlink and asserts install.zsh writes a regular
file (not through the symlink).
- tests/test-update-zsh-path-clobber.zsh — drives update.zsh --dry-run
through a clean stamped workbench and asserts the dirty-check does
not falsely flag template-owned paths.
Both tests verified red-state before the fix and green-state after.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
amit-t
added a commit
that referenced
this pull request
May 19, 2026
fix(version-check): recover devkit clone when DEVKIT_CLONE env unset (#17)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two install/upgrade bugs surfaced during the v1.1.0 real-LLM smoke against
wb-gitlore. Together they made everywb.upgradea no-op that wrongly reported a clean workbench as dirty and forwarded into a deleted worktree path. Both reproducible on macOS+zsh.Bug 1 —
install.zshcorruptsupdate.zshvia legacy symlinkinstall.zsh:142does:Earlier install.zsh versions installed
update.wbas a symlink (~/.local/bin/update.wb → update-workbench/update.zsh). The newcat >follows that symlink and overwrites the realupdate.zshwith the 3-line deprecation shim. Every re-install re-corrupts the clone:(The hard-coded worktree path is whatever
$SCRIPT_DIRwas the first timecat >ran, frequently a long-deleted feature worktree.)Fix:
rm -f "$DEPRECATED_SHIM"before the heredoc so any legacy symlink is severed first.Bug 2 —
update.zshclobbers$PATHvia zsh$pathspecial parameterupdate-workbench/update.zsh:146doeswhile IFS= read -r path; do ... done. In zsh, the lowercase$pathis a tied array that mirrors$PATH. Reading the first template path intopathrewritesPATHto that value (e.g.CLAUDE.md). Every subsequentgitcall in the loop hits "command not found" (exit 127), sogit diff --quietreturns non-zero and every template-owned path is falsely flagged as dirty:The user is told to commit changes that don't exist.
Fix: rename the loop variable from
pathtotpl_pathin all three while-loops (dirty check, dry-run diff stat, apply). Drop a one-line note in the dirty-check block so the next reader doesn't trip over the same gotcha.Regression tests
tests/test-install-update-wb-symlink-safe.zsh— sets up a fake HOME with the legacyupdate.wbsymlink, runsinstall.zsh, and asserts the realupdate.zshis byte-identical afterwards (shasum compare) and that the new shim is a regular file, not a symlink.tests/test-update-zsh-path-clobber.zsh— drivesupdate.zsh --dry-runagainst a fresh stamped workbench built fromtests/fixtures/bare-upstream/setup.sh, with one clean template-owned path (version.json). Asserts the dirty-check does not falsely flag the path and--dry-runreaches the diff stage.Both tests were verified red against the unfixed code and green after the fixes.
tests/run-all.zshpicks them up automatically.Test plan
wb.upgradefrom a stamped wb correctly detects a clean tree, pulls template-owned paths, and stamps.workbench-state/template-version.jsoninstall.zshre-run on a host with the legacy symlink does not corruptupdate.zsh(test catches this)Risk
Low. Both fixes are surgical, with regression tests. The
tpl_pathrename does not change any external interface. Therm -fbeforecat >is a no-op if the path didn't already exist.🤖 Generated with Claude Code