From d46d0e28b936be95f5e1e4f0503e000db614423d Mon Sep 17 00:00:00 2001 From: Johnny IV Young Ospino Date: Mon, 5 Oct 2026 13:53:30 -0500 Subject: [PATCH 1/4] fix(project): skip tool manifests without a Go version - 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 --- CHANGELOG.md | 1 + README.md | 5 +++- gos.sh | 36 +++++++++++++++++++------- tests/project-env.bash | 59 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a41f382..b02851a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this ### Fixed +- Project resolution skips `.tool-versions` files that only pin other languages, preserving `go.mod` and parent Go pins. Bash/Zsh auto-switching tracks both the unrelated manifest and the selected Go manifest so edits in the same directory invalidate the prompt cache. Incomplete explicit Go entries still fail closed. - Windows uninstall validates ownership, removes only known installed files, and preserves unrelated contents instead of recursively deleting the target directory. Existing installations without a receipt remain supported, with their ambiguous `LICENSE` left in place. - Windows installs stage replacements on the destination filesystem and restore previous files on publication failure. Failed fresh installs remove partial payloads; failed restoration keeps recovery files and reports their location. - Windows PATH edits refresh a stale process even when the registry already contains gos, remove process entries on uninstall, and preserve registry value types and unrelated entries. Tests use registry substitutes and a disposable native Windows key. diff --git a/README.md b/README.md index 56d76d3..802a1e2 100644 --- a/README.md +++ b/README.md @@ -449,7 +449,10 @@ gos updated: v1.9.0 -> v1.10.0 `gos use` searches from the current directory upward. At each directory level it prefers `.go-version`, then `.tool-versions` entries named `golang` or `go`, -then a `toolchain goX.Y.Z` directive in `go.mod`, then the `go X.Y` directive. +then a `toolchain goX.Y.Z` directive in `go.mod`, then the `go X.Y` directive. A +`.tool-versions` file that only lists other languages is skipped, so `go.mod` +and parent Go pins still apply. An explicit Go entry with a missing version +fails instead of silently selecting a different toolchain. ```bash gos pin 1.24.1 # writes .go-version diff --git a/gos.sh b/gos.sh index b182f9c..115eed8 100755 --- a/gos.sh +++ b/gos.sh @@ -2028,13 +2028,14 @@ _gos_read_tool_versions_file() { version="${2:-}" case "$tool" in go | golang) - [ -n "$version" ] || continue + [ -n "$version" ] || return 2 version="${version#go}" printf '%s\n' "$version" return 0 ;; esac - done <"$file" + done <"$file" || return 2 + # A readable multi-tool manifest without Go is not a Go version pin. return 1 } @@ -2078,9 +2079,14 @@ _gos_resolve_project_version() { candidate="${dir%/}/.tool-versions" if [ -f "$candidate" ]; then - version=$(_gos_read_tool_versions_file "$candidate") || return 1 - printf '%s|%s\n' "$version" "$candidate" - return 0 + if version=$(_gos_read_tool_versions_file "$candidate"); then + printf '%s|%s\n' "$version" "$candidate" + return 0 + else + # Status 1 means no Go entry: keep looking in go.mod and parents. + # An unreadable file or explicit incomplete Go entry must still fail. + [ "$?" -eq 1 ] || return 1 + fi fi candidate="${dir%/}/go.mod" @@ -4008,14 +4014,26 @@ __gos_auto_switch() { # directory still invalidates the cached PATH decision. local gos_version gos_bin gos_key gos_manifest="" gos_manifest_line local gos_dir="$PWD" gos_candidate gos_installed="$GOS_VERSIONS_DIR" + local gos_has_pin gos_tool_pattern='^[[:space:]]*(go|golang)([[:space:]]|$)' while :; do for gos_candidate in "${gos_dir%/}/.go-version" "${gos_dir%/}/.tool-versions" "${gos_dir%/}/go.mod"; do if [ -f "$gos_candidate" ]; then - gos_manifest="${gos_candidate}"$'\n' - while IFS= read -r gos_manifest_line || [ -n "$gos_manifest_line" ]; do + gos_manifest="${gos_manifest}${gos_candidate}"$'\n' + gos_has_pin=true + [ "${gos_candidate##*/}" != ".tool-versions" ] || gos_has_pin=false + if while IFS= read -r gos_manifest_line || [ -n "$gos_manifest_line" ]; do gos_manifest="${gos_manifest}${gos_manifest_line}"$'\n' - done <"$gos_candidate" 2>/dev/null || gos_manifest="unreadable:${gos_candidate}" - break 2 + if [[ $gos_manifest_line =~ $gos_tool_pattern ]]; then + gos_has_pin=true + fi + done <"$gos_candidate" 2>/dev/null; then + # Include unrelated .tool-versions files in the snapshot, but keep + # following the resolver to the actual Go pin. Either can change. + [ "$gos_has_pin" = false ] || break 2 + else + gos_manifest="${gos_manifest}unreadable:${gos_candidate}" + break 2 + fi fi done [ "$gos_dir" = "/" ] && break diff --git a/tests/project-env.bash b/tests/project-env.bash index c6f9cf1..5212c80 100644 --- a/tests/project-env.bash +++ b/tests/project-env.bash @@ -137,6 +137,36 @@ GOS_TEST_VERSIONS_DIR="${case_dir}/versions" run_gos "$case_dir" bash "$script" [ "$output" = "1.21" ] || fail "__project-version should keep an ambiguous bare minor, got: ${output}" pass "__project-version resolves a bare go.mod minor against installed versions" +# Multi-language repositories commonly pin Ruby/Node/Python with asdf while +# Go comes from go.mod or a parent pin. A manifest without Go is not a pin. +case_dir="${test_root}/project-tool-versions-fallback" +mkdir -p "${case_dir}/project/child" +printf 'nodejs 24.0.0\npython 3.13.0\n' >"${case_dir}/project/.tool-versions" +printf 'module example.com/multitool\n\ngo 1.20\ntoolchain go1.21.6\n' >"${case_dir}/project/go.mod" +run_gos "$case_dir" bash "$script" __project-version "${case_dir}/project" +assert_status 0 "$status" 'non-Go tool manifest fallback' "$output" +[ "$output" = "1.21.6" ] || fail "non-Go tool manifest masked go.mod: ${output}" +printf 'ruby 3.3.0\n' >"${case_dir}/project/child/.tool-versions" +run_gos "$case_dir" bash "$script" __project-version "${case_dir}/project/child" +[ "$output" = "1.21.6" ] || fail "non-Go tool manifest masked a parent Go pin: ${output}" +printf 'golang 1.20.0\n' >"${case_dir}/project/.tool-versions" +run_gos "$case_dir" bash "$script" __project-version "${case_dir}/project" +[ "$output" = "1.20.0" ] || fail "explicit Go tool version lost precedence: ${output}" +printf 'golang\n' >"${case_dir}/project/.tool-versions" +run_gos "$case_dir" bash "$script" use --print "${case_dir}/project" +assert_nonzero_status "$status" 'incomplete Go entry must not fall back' "$output" +printf 'ruby 3.3.0\n' >"${case_dir}/project/.tool-versions" +chmod 000 "${case_dir}/project/.tool-versions" +if [ ! -r "${case_dir}/project/.tool-versions" ]; then + run_gos "$case_dir" bash "$script" use --print "${case_dir}/project" + assert_nonzero_status "$status" 'unreadable tool manifest must not fall back' "$output" +else + pass 'unreadable tool manifest check skipped: this host can read mode 000 files' +fi +chmod 600 "${case_dir}/project/.tool-versions" +[ ! -s "${case_dir}/urls.log" ] || fail 'manifest fallback must remain offline' +pass 'project resolution ignores unrelated tool versions but preserves explicit Go pins' + case_dir="${test_root}/env-auto" mkdir -p "${case_dir}/project" "${case_dir}/missing" "${case_dir}/versions/go1.21.6/bin" "${case_dir}/versions/go1.20.0/bin" "${case_dir}/bin" printf '1.21.6\n' >"${case_dir}/project/.go-version" @@ -190,6 +220,20 @@ rm .go-version printf 'module example.com/edit\n\ngo 1.21\n' >go.mod __gos_auto_switch case "$GOS_AUTO_BIN" in */go1.21.6/bin) ;; *) exit 12 ;; esac +# An unrelated asdf file must not hide the go.mod used by the resolver or +# keep its contents out of the prompt cache's invalidation snapshot. +printf 'ruby 3.3.0\n' >.tool-versions +__gos_auto_switch +case "$GOS_AUTO_BIN" in */go1.21.6/bin) ;; *) exit 16 ;; esac +printf 'module example.com/edit\n\ngo 1.20\n' >go.mod +__gos_auto_switch +case "$GOS_AUTO_BIN" in */go1.20.0/bin) ;; *) exit 17 ;; esac +printf 'golang 1.21.6\n' >.tool-versions +__gos_auto_switch +case "$GOS_AUTO_BIN" in */go1.21.6/bin) ;; *) exit 18 ;; esac +rm .tool-versions +printf 'module example.com/edit\n\ngo 1.21\n' >go.mod +__gos_auto_switch # Installing a second matching patch makes this bare minor ambiguous. mkdir -p "$GOS_VERSIONS_DIR/go1.21.7/bin" cat "$GOS_VERSIONS_DIR/go1.21.6/bin/go" >"$GOS_VERSIONS_DIR/go1.21.7/bin/go" @@ -205,6 +249,21 @@ case "$GOS_AUTO_BIN" in */go1.20.0/bin) ;; *) exit 13 ;; esac rm go.mod __gos_auto_switch [ -z "${GOS_AUTO_BIN:-}" ] +# A child manifest with no Go must also observe edits to a parent Go pin. +mkdir -p nested +printf 'ruby 3.3.0\n' >nested/.tool-versions +printf '1.21.6\n' >.go-version +cd nested +__gos_auto_switch +case "$GOS_AUTO_BIN" in */go1.21.6/bin) ;; *) exit 19 ;; esac +printf '1.20.0\n' >../.go-version +__gos_auto_switch +case "$GOS_AUTO_BIN" in */go1.20.0/bin) ;; *) exit 20 ;; esac +cd .. +rm .go-version nested/.tool-versions +rmdir nested +__gos_auto_switch +[ -z "${GOS_AUTO_BIN:-}" ] if [ -n "${ZSH_VERSION:-}" ]; then case " ${precmd_functions[*]} " in *" __gos_auto_switch "*) ;; *) exit 14 ;; esac fi From bd13b8d38fd7ae879aba05ed801294f022f5e149 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 15:28:53 +0000 Subject: [PATCH 2/4] fix(env): keep the auto-switch hook from clobbering regex matches __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 Claude-Session: https://claude.ai/code/session_01ExmnfUngrDYjAheGvYwMnn --- gos.sh | 11 +++++++---- tests/project-env.bash | 8 ++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/gos.sh b/gos.sh index 115eed8..615c642 100755 --- a/gos.sh +++ b/gos.sh @@ -4014,7 +4014,7 @@ __gos_auto_switch() { # directory still invalidates the cached PATH decision. local gos_version gos_bin gos_key gos_manifest="" gos_manifest_line local gos_dir="$PWD" gos_candidate gos_installed="$GOS_VERSIONS_DIR" - local gos_has_pin gos_tool_pattern='^[[:space:]]*(go|golang)([[:space:]]|$)' + local gos_has_pin while :; do for gos_candidate in "${gos_dir%/}/.go-version" "${gos_dir%/}/.tool-versions" "${gos_dir%/}/go.mod"; do if [ -f "$gos_candidate" ]; then @@ -4023,9 +4023,12 @@ __gos_auto_switch() { [ "${gos_candidate##*/}" != ".tool-versions" ] || gos_has_pin=false if while IFS= read -r gos_manifest_line || [ -n "$gos_manifest_line" ]; do gos_manifest="${gos_manifest}${gos_manifest_line}"$'\n' - if [[ $gos_manifest_line =~ $gos_tool_pattern ]]; then - gos_has_pin=true - fi + # A case pattern, not [[ =~ ]]: this runs in the user's interactive + # shell on every prompt and must not clobber BASH_REMATCH or zsh's + # MATCH/match (or depend on zsh's RE_MATCH_PCRE module). + case "${gos_manifest_line#"${gos_manifest_line%%[![:space:]]*}"}" in + go | golang | go[[:space:]]* | golang[[:space:]]*) gos_has_pin=true ;; + esac done <"$gos_candidate" 2>/dev/null; then # Include unrelated .tool-versions files in the snapshot, but keep # following the resolver to the actual Go pin. Either can change. diff --git a/tests/project-env.bash b/tests/project-env.bash index 5212c80..2d4fecf 100644 --- a/tests/project-env.bash +++ b/tests/project-env.bash @@ -228,9 +228,17 @@ case "$GOS_AUTO_BIN" in */go1.21.6/bin) ;; *) exit 16 ;; esac printf 'module example.com/edit\n\ngo 1.20\n' >go.mod __gos_auto_switch case "$GOS_AUTO_BIN" in */go1.20.0/bin) ;; *) exit 17 ;; esac +# The hook runs in the user's interactive shell on every prompt, so scanning +# a Go entry must not clobber the user's last regex match. +[[ user-match =~ user-match ]] printf 'golang 1.21.6\n' >.tool-versions __gos_auto_switch case "$GOS_AUTO_BIN" in */go1.21.6/bin) ;; *) exit 18 ;; esac +if [ -n "${ZSH_VERSION:-}" ]; then + [ "${MATCH:-}" = user-match ] || exit 21 +else + [ "${BASH_REMATCH[0]:-}" = user-match ] || exit 21 +fi rm .tool-versions printf 'module example.com/edit\n\ngo 1.21\n' >go.mod __gos_auto_switch From 4362759c48f939f307b45c01d88b90f04f377118 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 15:41:12 +0000 Subject: [PATCH 3/4] fix(project): split .tool-versions fields without pathname expansion 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 Claude-Session: https://claude.ai/code/session_01ExmnfUngrDYjAheGvYwMnn --- CHANGELOG.md | 2 +- gos.sh | 11 +++++++---- tests/project-env.bash | 10 ++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b02851a..9f3a510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this ### Fixed -- Project resolution skips `.tool-versions` files that only pin other languages, preserving `go.mod` and parent Go pins. Bash/Zsh auto-switching tracks both the unrelated manifest and the selected Go manifest so edits in the same directory invalidate the prompt cache. Incomplete explicit Go entries still fail closed. +- Project resolution skips `.tool-versions` files that only pin other languages, preserving `go.mod` and parent Go pins. Bash/Zsh auto-switching tracks both the unrelated manifest and the selected Go manifest so edits in the same directory invalidate the prompt cache. Incomplete explicit Go entries still fail closed, and the first `go`/`golang` entry decides (a bare entry no longer lets a later line win). `.tool-versions` fields are no longer pathname-expanded, so `golang *` cannot select a version from file names in the working directory. - Windows uninstall validates ownership, removes only known installed files, and preserves unrelated contents instead of recursively deleting the target directory. Existing installations without a receipt remain supported, with their ambiguous `LICENSE` left in place. - Windows installs stage replacements on the destination filesystem and restore previous files on publication failure. Failed fresh installs remove partial payloads; failed restoration keeps recovery files and reports their location. - Windows PATH edits refresh a stale process even when the registry already contains gos, remove process entries on uninstall, and preserve registry value types and unrelated entries. Tests use registry substitutes and a disposable native Windows key. diff --git a/gos.sh b/gos.sh index 615c642..566eaa6 100755 --- a/gos.sh +++ b/gos.sh @@ -2022,10 +2022,13 @@ _gos_read_tool_versions_file() { line=$(printf '%s' "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [ -z "$line" ] && continue - # shellcheck disable=SC2086 # Intentional field split: .tool-versions is whitespace-delimited. - set -- $line - tool="${1:-}" - version="${2:-}" + # Split the whitespace-delimited fields with parameter expansion, not an + # unquoted `set -- $line`: field splitting would also glob, so an entry + # such as `golang *` must never expand against the current directory. + tool="${line%%[[:space:]]*}" + version="${line#"$tool"}" + version="${version#"${version%%[![:space:]]*}"}" + version="${version%%[[:space:]]*}" case "$tool" in go | golang) [ -n "$version" ] || return 2 diff --git a/tests/project-env.bash b/tests/project-env.bash index 2d4fecf..893b8ce 100644 --- a/tests/project-env.bash +++ b/tests/project-env.bash @@ -155,6 +155,16 @@ run_gos "$case_dir" bash "$script" __project-version "${case_dir}/project" printf 'golang\n' >"${case_dir}/project/.tool-versions" run_gos "$case_dir" bash "$script" use --print "${case_dir}/project" assert_nonzero_status "$status" 'incomplete Go entry must not fall back' "$output" +# Fields are split without pathname expansion: a stray file named like a +# version in the caller's directory must not turn `golang *` into a pin. +mkdir -p "${case_dir}/decoy" +: >"${case_dir}/decoy/1.21.6" +printf 'golang *\n' >"${case_dir}/project/.tool-versions" +cd "${case_dir}/decoy" +run_gos "$case_dir" bash "$script" use --print "${case_dir}/project" +cd "$OLDPWD" +assert_nonzero_status "$status" 'a glob version must not expand against the working directory' "$output" +assert_not_contains "$output" '1.21.6' 'tool versions fields are not pathname-expanded' printf 'ruby 3.3.0\n' >"${case_dir}/project/.tool-versions" chmod 000 "${case_dir}/project/.tool-versions" if [ ! -r "${case_dir}/project/.tool-versions" ]; then From e5eae8e32877486598ffabe9091fbf033f226237 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 04:01:08 +0000 Subject: [PATCH 4/4] fix(project): name incomplete Go entries and quiet unreadable manifests - report which .tool-versions entry has no version before failing closed - treat a go-prefix-only entry such as go go as incomplete - trim .tool-versions lines with parameter expansion instead of sed per line - keep the prompt hook silent for an unreadable manifest - scan for a Go entry only while the hook has not found one - cover first-entry precedence, go go and offline checks per call Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ExmnfUngrDYjAheGvYwMnn --- gos.sh | 35 ++++++++++++++++++++++++----------- tests/project-env.bash | 14 ++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/gos.sh b/gos.sh index 566eaa6..a9f8543 100755 --- a/gos.sh +++ b/gos.sh @@ -2019,7 +2019,9 @@ _gos_read_tool_versions_file() { local file="$1" line tool version while IFS= read -r line || [ -n "$line" ]; do line=${line%%#*} - line=$(printf '%s' "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + # Trim with parameter expansion, not a sed subprocess per line: the + # resolver can now read a .tool-versions at every directory level. + line="${line#"${line%%[![:space:]]*}"}" [ -z "$line" ] && continue # Split the whitespace-delimited fields with parameter expansion, not an @@ -2031,8 +2033,11 @@ _gos_read_tool_versions_file() { version="${version%%[[:space:]]*}" case "$tool" in go | golang) - [ -n "$version" ] || return 2 version="${version#go}" + if [ -z "$version" ]; then + _gos_error "${file}: the ${tool} entry has no version." + return 2 + fi printf '%s\n' "$version" return 0 ;; @@ -4024,15 +4029,23 @@ __gos_auto_switch() { gos_manifest="${gos_manifest}${gos_candidate}"$'\n' gos_has_pin=true [ "${gos_candidate##*/}" != ".tool-versions" ] || gos_has_pin=false - if while IFS= read -r gos_manifest_line || [ -n "$gos_manifest_line" ]; do - gos_manifest="${gos_manifest}${gos_manifest_line}"$'\n' - # A case pattern, not [[ =~ ]]: this runs in the user's interactive - # shell on every prompt and must not clobber BASH_REMATCH or zsh's - # MATCH/match (or depend on zsh's RE_MATCH_PCRE module). - case "${gos_manifest_line#"${gos_manifest_line%%[![:space:]]*}"}" in - go | golang | go[[:space:]]* | golang[[:space:]]*) gos_has_pin=true ;; - esac - done <"$gos_candidate" 2>/dev/null; then + # The group owns the 2>/dev/null: a redirection listed after the + # failing <"$gos_candidate" would not silence its error, which would + # then print on every prompt for an unreadable manifest. + if { + while IFS= read -r gos_manifest_line || [ -n "$gos_manifest_line" ]; do + gos_manifest="${gos_manifest}${gos_manifest_line}"$'\n' + # Only an unrelated-so-far .tool-versions needs the Go-entry scan. + # A case pattern, not [[ =~ ]]: this runs in the user's interactive + # shell on every prompt and must not clobber BASH_REMATCH or zsh's + # MATCH/match (or depend on zsh's RE_MATCH_PCRE module). + if [ "$gos_has_pin" = false ]; then + case "${gos_manifest_line#"${gos_manifest_line%%[![:space:]]*}"}" in + go | golang | go[[:space:]]* | golang[[:space:]]*) gos_has_pin=true ;; + esac + fi + done <"$gos_candidate" + } 2>/dev/null; then # Include unrelated .tool-versions files in the snapshot, but keep # following the resolver to the actual Go pin. Either can change. [ "$gos_has_pin" = false ] || break 2 diff --git a/tests/project-env.bash b/tests/project-env.bash index 893b8ce..976ed5e 100644 --- a/tests/project-env.bash +++ b/tests/project-env.bash @@ -155,6 +155,18 @@ run_gos "$case_dir" bash "$script" __project-version "${case_dir}/project" printf 'golang\n' >"${case_dir}/project/.tool-versions" run_gos "$case_dir" bash "$script" use --print "${case_dir}/project" assert_nonzero_status "$status" 'incomplete Go entry must not fall back' "$output" +assert_contains "$output" 'golang entry has no version' 'incomplete Go entry names the cause' +[ ! -s "${case_dir}/urls.log" ] || fail 'incomplete Go entry must stay offline' +# The first Go entry decides: a bare entry must not let a later line win, and +# a `go` prefix that leaves nothing behind is just as incomplete. +printf 'golang\ngo 1.20.0\n' >"${case_dir}/project/.tool-versions" +run_gos "$case_dir" bash "$script" use --print "${case_dir}/project" +assert_nonzero_status "$status" 'a bare first Go entry must not defer to a later line' "$output" +assert_not_contains "$output" '1.20.0' 'a later Go entry must not win over a bare first one' +printf 'go go\n' >"${case_dir}/project/.tool-versions" +run_gos "$case_dir" bash "$script" use --print "${case_dir}/project" +assert_nonzero_status "$status" 'a go-prefix-only Go entry is incomplete' "$output" +assert_contains "$output" 'go entry has no version' 'go-prefix-only entry names the cause' # Fields are split without pathname expansion: a stray file named like a # version in the caller's directory must not turn `golang *` into a pin. mkdir -p "${case_dir}/decoy" @@ -165,6 +177,8 @@ run_gos "$case_dir" bash "$script" use --print "${case_dir}/project" cd "$OLDPWD" assert_nonzero_status "$status" 'a glob version must not expand against the working directory' "$output" assert_not_contains "$output" '1.21.6' 'tool versions fields are not pathname-expanded' +# run_gos truncates urls.log on every call, so check after each use --print. +[ ! -s "${case_dir}/urls.log" ] || fail 'a glob Go entry must stay offline' printf 'ruby 3.3.0\n' >"${case_dir}/project/.tool-versions" chmod 000 "${case_dir}/project/.tool-versions" if [ ! -r "${case_dir}/project/.tool-versions" ]; then