Keep wt numbering stable when agent worktrees exist - #96
Open
mattmenefee wants to merge 2 commits into
Open
Conversation
`git worktree list` sorts linked worktrees by directory basename, and Claude Code creates throwaway checkouts for its subagents under `.claude/worktrees/agent-<id>`. Those sort in among the permanent worktrees rather than after them, so a running agent renumbered the list mid-session and `wt 2` stopped meaning the same checkout it meant a moment earlier. Route both `wt` and `wtl` through a `wtlist` helper that holds the temporary entries back and prints them after everything else. They stay visible and reachable by number, but they no longer displace the permanent worktrees ahead of them.
mattmenefee
force-pushed
the
stabilize-wt-worktree-numbering
branch
from
August 5, 2026 22:00
c942390 to
d176b63
Compare
The reordering added in the previous commit tested `$1` from
`git worktree list`, which stops at the first space. A worktree beneath
a path containing a space therefore never matched the
`.claude/worktrees/` marker, so agent checkouts stayed interleaved and
went on renumbering the list — the exact problem the change set out to
prevent. The same truncation reached `cd`, which would land silently in
a prefix directory rather than the worktree whenever that prefix
happened to exist.
`wt` now reads `git worktree list --porcelain -z` into an array.
NUL-delimited records are the only form that survives a path holding a
space or a newline; plain `--porcelain` leaves a newline-bearing path
split across two lines, which would invent an entry and pull the
numbering out of step. `wtl` still reorders git's human-readable
listing so it keeps the commit and branch columns, matching against the
whole line — equivalent to matching the path, because git forbids a
refname component beginning with a dot.
Indexing an array also retires `sed -n "${1}p"`, which was a sed
program rather than a subscript: `wt '1w /some/file'` truncated an
arbitrary file, `wt '1r /some/file'` read one back, and a bare `wt`
printed every line into `cd`. A numeric guard now turns away anything
that is not a plain number, closing those off for good.
Both refusals report on stderr and return non-zero. A missing worktree
used to be announced on stdout while `wt` still exited successfully, so
`wt 9 && bin/dev` went on to run the command from wherever the shell
already stood.
Error suppression moves down into the path helper as well. Sharing it
through the listing function had silenced `wtl` outside a git
repository, where it printed nothing at all in place of git's
explanation.
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
git worktree listsorts linked worktrees by directory basename, so the throwaway checkouts Claude Code creates for its subagents under.claude/worktrees/agent-<id>sorted in among the permanent worktrees rather than after them. A running agent renumbered the list mid-session, andwt 2stopped resolving to the same checkout it meant a moment earlierwtlisthelper that holds the temporary entries back and prints them after everything else. Agent worktrees stay visible and reachable by number without displacing the permanent ones ahead of themwtresolves paths fromgit worktree list --porcelain -z, read into an array. NUL-delimited records are the only format that keeps a path containing a space or a newline intact — plain--porcelainleaves a newline-bearing path split across two lines, which would invent an entry and pull the numbering out of step.wtlkeeps reordering git's human-readable listing so it retains the commit and branch columns, matching against the whole line, which is equivalent to matching the path because git forbids a refname component beginning with a dotsed -n "${1}p", which was a sed program rather than a subscript:wt '1w /some/file'truncated an arbitrary file,wt '1r /some/file'read one back, and a barewtprinted every line intocd. A numeric guard now turns away anything that is not a plain numberwtstill exited successfully, sowt 9 && bin/devwent on to run the command from wherever the shell already stood_wt_pathsis nameddirrather than the obviouspathbecause zsh tiespathtoPATH— a local by that name empties it andgitstops resolving inside the function. There is a comment on the declaration so a future tidy-up does not reintroduce itRequires
source ~/.zshrcto pick up the new functions; shells already running keep the oldwt.Test plan
zsh -n home/.zshrcparsesMy Code/.claude/worktrees/—wtllists the agent entry last, and the earlierindex($1, ...)form left it interleaved because$1stops at the first spacewt Nlands in the directorywtlnumbers N for every entry, including the space-containing and newline-containing paths that the previousawk '{print $1}'truncatedgit worktree list --porcelainwithout-zsplits a newline-bearing path across two lines while-zkeeps the record whole, and that the human-readable listing C-quotes it onto one line — the reason the two consumers read different formatswt abc,wt -1,wt '',wt '1p;2', andwt '1w /tmp/CLOBBER_ME'are all refused withUsage: wt <number>and exit 1, and thewattempt created no filewt 0andwt 99report not found on stderr and exit 1wt 2 && …runs the following command andwt 99 && …does not, withwt 99 || …firing as expectedwtlsurfacesfatal: not a git repositoryagain outside a repository, where the first revision of this branch printed nothing at all