Repository navigation
fix(project): resolve Go pins past unrelated tool manifests - #44
Open
johnny4young wants to merge 3 commits into
Open
johnny4young wants to merge 3 commits into
johnny4young wants to merge 3 commits into
Conversation
- Preserve explicit and unreadable Go pin failures - Track fallback manifest changes in the Bash and Zsh prompt cache - Cover local and parent manifests with offline regression tests
This was referenced Oct 5, 2026
johnny4young
marked this pull request as ready for review
October 6, 2026 03:49
__gos_auto_switch runs in the user's interactive shell on every prompt. Detecting a Go entry with [[ =~ ]] overwrote the user's BASH_REMATCH (and zsh's MATCH/match, or required zsh/pcre under RE_MATCH_PCRE). Use an equivalent case pattern instead and cover the user's match surviving in both Bash and Zsh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ExmnfUngrDYjAheGvYwMnn
johnny4young
commented
Oct 6, 2026
johnny4young
left a comment
Owner
Author
There was a problem hiding this comment.
Review: resolve Go pins past unrelated tool manifests
What it does: _gos_read_tool_versions_file now returns 0 for a Go pin, 1 for "readable, no Go entry", and 2 for an unreadable file or a go/golang entry without a version. _gos_resolve_project_version continues to go.mod and parent directories only on status 1. The Bash/Zsh prompt hook (__gos_auto_switch) applies the same rule when it builds its cache-invalidation snapshot, and now records every manifest it walked past, not just the last one.
Verdict: ready after the fix I pushed.
Fixed (pushed bd13b8d)
- 🟠
gos.sh__gos_auto_switch: the hook detected a Go line with[[ $line =~ $gos_tool_pattern ]]. The hook runs in the user's interactive shell on every prompt, so this overwrote the user'sBASH_REMATCHand, in Zsh,MATCH/match/MBEGIN/…. Zsh users withsetopt RE_MATCH_PCREwould also needzsh/pcreon every prompt. Repro:[[ "user value" =~ (user) ]], then a prompt in a dir whose.tool-versionshasgolang 1.2.3, givesBASH_REMATCH[0]=golang. I replaced it with an equivalent subprocess-freecaseon the left-trimmed line:go | golang | go[[:space:]]* | golang[[:space:]]*. It gives the same results as the regex forgolang 1.2, leading spaces/tabs, a barego/golang,golang\r,go-task,goplsand# golangin both bash 5.2 and zsh 5.9.tests/project-env.bashnow asserts that the user's match survives in both shells, and that test fails without the fix.- Validation: full suite as a non-root user gave 31/31 passed (with zsh installed, so the Zsh hook path ran). ShellCheck 0.11.0, shfmt 3.13.1,
bash -nandsync-command-surfaces --checkwere clean. CI will re-run for macOS Bash 3.2. The constructs (${v%%[[:space:]]*},casebracket classes) are Bash 3.2-safe.
- Validation: full suite as a non-root user gave 31/31 passed (with zsh installed, so the Zsh hook path ran). ShellCheck 0.11.0, shfmt 3.13.1,
Checked and fine
if version=$(…); then …; else [ "$?" -eq 1 ] || return 1; fi:$?in theelsebranch is the condition's status, so 2 correctly fails closed. Thewhileloop's normal status is 0 (its last body command is acase), so|| return 2triggers only on a redirect failure. Unreadable files and an explicitgolangwith no version are both covered by tests.- Cache key: a non-Go
.tool-versionsfile is included in the snapshot and the walk continues, so edits to it or to thego.mod/parent pin invalidate the cache. Treating a line as a Go pin when the resolver wouldn't can only over-invalidate, never serve a stale result. go.modparsing is untouched (toolchainwins overgo, firstgodirective only).
Notes / nits
- 🟡 Behavior change worth a changelog word: previously a bare
golangline was skipped and a latergolang Xline in the same file could still win. Now the firstgo/golangentry decides and a bare one fails. That matches asdf's first-match semantics, and the README already says it fails. - 🟡 Pre-existing:
_gos_read_tool_versions_filefield-splits withset -- $linewithoutset -f, so a line such asgolang *is pathname-expanded against$PWD. Harmless in practice, butread -r tool version _ <<<"$line"(or a localset -f) would avoid the glob.
Generated by Claude Code
The unquoted `set -- $line` split also globbed, so an entry such as `golang *` expanded against the caller's working directory: a stray file named 1.21.6 there selected go1.21.6. Split the fields with parameter expansion instead, which is subprocess-free and Bash 3.2-safe, and cover the decoy-file case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ExmnfUngrDYjAheGvYwMnn
This was referenced Oct 6, 2026
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
In a multi-language repository,
.tool-versionsoften lists only Ruby, Node or Python, next to a validgo.mod. The resolver used to stop at that file and report no project version, and a child directory's unrelated.tool-versionshid a parent's Go pin. Fixing only the resolver would have left a second bug: Bash/Zsh auto-switching cached the first manifest it found, so edits to the real fallbackgo.modor the parent pin never refreshedPATH.What changed
_gos_read_tool_versions_file/_gos_resolve_project_version) uses a three-way status:go/golangentry, so resolution continues togo.modand then parent directories;go/golangentry has no version, so resolution fails closed.__gos_auto_switch): the pure-shell prompt snapshot includes every manifest consulted until the effective Go pin is found. Edits to the unrelated file, thego.modor the parent pin all invalidate the cache, and the subprocess-free fast path for an unchanged prompt is kept..tool-versionslines now uses parameter expansion (no globbing; see follow-ups).tests/project-env.bashcovering:Behavior change (documented)
golang(orgo) line without a version used to be skipped, so a latergolang Xline in the same file could still win. Now the firstgo/golangentry decides, and a bare one fails closed instead of silently picking another toolchain. This matches asdf's first-match semantics..tool-versionsfile with no Go entry no longer makesgos use/gos run --fail. Resolution continues togo.modand then parent directories.Why
Repositories that pin other runtimes with asdf/mise would otherwise get "no version found" despite a valid
go.mod, and their prompt would keep a stalePATH.How it was tested
tests/project-env.bash: the fallback regression fails against the originalgos.sh. Both new follow-up regressions also fail without their fixes.4362759, as a non-root user with zsh 5.9 installed (so the Zsh hook path runs): 31/31 passed.bash -n,scripts/sync-command-surfaces.bash --checkandgit diff --checkare clean.golang 1.22.1→1.22.1;golang<TAB>1.21.0 extra→1.21.0;go go1.20.3→1.20.3;nodejs 20→ no pin;golang→ fails;golang *→ rejected as'*'instead of being expanded.4362759: all 10 jobs and all 11 checks green, including native macOS Bash 3.2, Linux Fish and Windows Bash/PowerShell. Previous qualification: run 37359410297 ond46d0e2.Review follow-ups
bd13b8dfix(env): keep the auto-switch hook from clobbering regex matches. The hook runs in the user's interactive shell on every prompt. Detecting a Go line with[[ =~ ]]overwrote the user'sBASH_REMATCH(and Zsh'sMATCH/match, and neededzsh/pcreunderRE_MATCH_PCRE). It now uses an equivalentcaseon the left-trimmed line. The test asserts the user's match survives in Bash and Zsh.4362759fix(project): split.tool-versionsfields without pathname expansion. The unquotedset -- $linealso globbed, so withgolang *and a stray file named1.21.6in the caller's working directory, gos selectedgo1.21.6. Fields are now split with parameter expansion, which is subprocess-free and safe on Bash 3.2 (this resolver always runs under Bash). The regression uses that exact decoy file.Decisions taken
PATH.Known limitations & follow-ups
skip_assertionlike the others.Merge order
Independent of the other open PRs. It merges cleanly with #45, #46, #47, #48 and #49. Builds on the already merged #42 and #43.