Skip to content

fix: don't name shell variables "path" - zsh ties it to $PATH - #7

Merged
stilliard merged 1 commit into
mainfrom
fix/zsh-path-local
Aug 12, 2026
Merged

fix: don't name shell variables "path" - zsh ties it to $PATH#7
stilliard merged 1 commit into
mainfrom
fix/zsh-path-local

Conversation

@stilliard

Copy link
Copy Markdown
Owner

The bug

wt merged --rm fails partway through under zsh, leaving the worktree in place:

wt: remove 1 worktree(s) and their Claude Code sessions? [y/N] y
git: command not found
awk: command not found
wt: no worktree matching 'worktree-unchat-nudges'

In zsh the path array is tied to PATH. A local path (or a bare read -r path) empties PATH for that function's scope and every function it calls, so git/awk stop resolving mid-run.

This explains the failure ordering exactly: in _wt_merged the awk/grep/confirm prompt all run before local main_wt path branch failed=0; the first command after it is git worktree list | awk — the first error seen. _wt_rm_wt_resolve then fail for the same reason, producing the "no worktree matching" tail. PATH is restored when the function returns, which is why the shell keeps working afterwards.

zsh -c 'f() { local path; echo "PATH=[$PATH]"; }; f'   # → PATH=[]

The fix

Rename to wt_path at all four sites — declaration and every use:

  • _wt_claude_table and _wt_merged — the two reachable from the reported failure
  • _wt_claude_rm_sessions, _wt_run_adhoc_hook — same latent bug

Renaming only the declaration would be worse: the leftover read -r path would then write straight into zsh's tied array and clobber PATH permanently rather than for the function's scope.

WT_PATH, the hook env var, is not a special name and is unchanged — the hook contract is untouched.

Also drops the comment claiming the claude CLI mutates the caller's PATH. That was a misdiagnosis of this same bug (a subprocess cannot alter its parent's environment); the real reason the jq/column lookups had to happen early was local path in _wt_claude_table. The up-front binary resolution stays — the one-shot session fetch is still worth it.

Tests

The suite runs under bash, where this bug class is completely invisible, so test/zsh.bats adds:

  1. a behavioural wt merged --rm run under a real zsh (skips when zsh is absent)
  2. a static grep guard against any local/read naming a variable path, covering functions with no zsh coverage

Both fail against the pre-fix wt.sh and pass after. Full suite: 55/55 green.

🤖 Generated with Claude Code

In zsh the `path` array is tied to `PATH`, so a `local path` (or a bare
`read -r path`) empties PATH for that function's scope and everything it
calls. `wt merged --rm` hit this at `local main_wt path branch`: every
subsequent git/awk call failed with "command not found", _wt_resolve
returned nothing, and removal aborted with "no worktree matching".

Rename to wt_path at all four sites - declaration and every use. Renaming
only the declaration would be worse: the leftover `read -r path` would
then write into zsh's tied array and clobber PATH permanently rather than
for the function's scope.

WT_PATH, the hook env var, is not a special name and is unchanged.

Also drop the claim that the `claude` CLI mutates the caller's PATH - a
misdiagnosis of this same bug (a subprocess cannot alter its parent's
environment). The up-front binary resolution stays; the one-shot session
fetch is still worth it.

Tests run under bash, where this bug class is invisible, so add a
behavioural zsh test (skipped when zsh is absent) plus a static grep
guard. Both fail against the pre-fix wt.sh.

Co-Authored-By: Claude Opus 5 <[email protected]>
Copilot AI lite review requested due to automatic review settings August 12, 2026 21:46

@stilliard stilliard left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@stilliard
stilliard merged commit c3d12ea into main Aug 12, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a zsh-specific failure mode where naming a shell variable path (via local path or read -r path) can clobber PATH due to zsh’s tied path array, causing downstream commands like git/awk to stop resolving during wt merged --rm.

Changes:

  • Renames shell variables previously named path to wt_path in affected functions to avoid zsh’s path/PATH coupling.
  • Removes an incorrect comment about the claude CLI mutating the caller’s PATH.
  • Adds zsh-focused tests: a behavioral wt merged --rm run under real zsh (skipped if absent) plus a static guard to prevent reintroducing path as a shell variable name.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
wt.sh Renames shell variable path usages to wt_path and updates related comments to prevent zsh PATH clobbering.
test/zsh.bats Adds a zsh behavioral regression test and a static grep-based guard against path shell variables.
test/helpers.bash Exposes WT_SH path for tests so zsh can source the same script location reliably.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants