fix: stop PATH growing on every activation, guard the bash cd-hook, ship linux-aarch64 - #41
Conversation
Every activation emitted "export PATH=<bin>:$PATH", prepending its bin directory to the live PATH without removing the entry the previous activation had put there. Each switch therefore left its predecessor behind, and in bash the cd hook runs from PROMPT_COMMAND -- after every command, not just after a cd -- so a shell sitting in a directory with a .php-version file grew PATH by one entry per prompt. PATH manipulation moves out of the emitted shell code: the wrapper runs pvm as a direct child, so the Rust process already sees the PATH its output will be eval'd into. fs::path_without_versions() drops every $PVM_DIR/versions entry and Shell::path bakes the resulting value in literally, which also lets deactivate() stop shelling out to tr/grep/paste. Two related guards: Bash::use_on_cd returns early unless $PWD actually changed, and wrapper_fn only prepends $PVM_DIR/bin when it is not already on PATH, so re-sourcing the rc file in a nested shell stops duplicating that entry too. pvm env now prints the default-version activation before the wrapper, because the literal snapshot cannot contain the $PVM_DIR/bin entry the wrapper adds one line later. Verified in bash, zsh and fish: six consecutive switches leave four PATH entries, and pvm use system restores the original PATH.
get_target_triple() resolves linux-aarch64 and install.sh maps uname -m to pvm-linux-aarch64.tar.gz, but the build matrix only produced linux-x86_64 and the two macOS targets. On ARM Linux both the install script and pvm self-update fetched an asset that was never uploaded and failed with a 404. Builds natively on the ubuntu-24.04-arm runner, so no cross toolchain is needed.
posix and fish each had their own "drop empty entries, render them" pass, and posix_deactivate had neither -- it joined the list raw, so an inherited "::" survived deactivation while activation stripped it. One path_entries() feeding a per-shell assignment renderer removes the duplication and the asymmetry, and taking &str instead of an owned iterator drops a String allocation per entry on the fish paths. Test PATHs move to synthetic /opt/tool-* entries so no assertion depends on how the host or CI runner lays out its directories, and the two snippet guards are asserted by relative position (guard before the thing it guards) rather than by a verbatim copy of the source line, which broke on any reformatting. Both were confirmed to fail with their guard removed.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR changes shell PATH generation to explicitly preserve non-managed entries, updates activation and deactivation flows, adds regression coverage, documents the shell behavior, and adds an ARM Linux release build targeting ChangesShell PATH flow
ARM release build
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Command
participant PathFilter
participant Shell
participant EnvUpdateFile
Command->>PathFilter: obtain unmanaged PATH entries
PathFilter-->>Command: return filtered entries
Command->>Shell: generate activation or deactivation snippet
Shell-->>EnvUpdateFile: write complete PATH and marker updates
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
## [2.0.1](v2.0.0...v2.0.1) (2026-07-24) ### Bug Fixes * stop PATH growing on every activation, guard the bash cd-hook, ship linux-aarch64 ([#41](#41)) ([0b7b39a](0b7b39a))
|
🎉 This PR is included in version 2.0.1 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
Three defects found during a read of the codebase. Each is a
fix:on its own commit, plus one follow-up refactor.1. PATH grew on every activation
Shell::pathemittedexport PATH=<bin>:"$PATH"without removing the entry the previous activation had put there, so every switch left its predecessor behind:Combined with defect 2 this meant one extra PATH entry per shell prompt in any directory holding a
.php-versionfile — within minutes PATH is hundreds of entries, everyexec*()gets more expensive andecho $PATHis unreadable.The fix moves PATH manipulation out of the emitted shell code. The wrapper runs
pvmas a direct child, so the Rust process already sees exactly the PATH its output will be eval'd into:fs::path_without_versions()drops every$PVM_DIR/versionsentry andShell::pathbakes the resulting value in literally.deactivate()uses the same list and no longer shells out totr/grep/paste.Two related guards came along, same root cause:
Bash::use_on_cdreturns early unless$PWDactually changed.wrapper_fnonly prepends$PVM_DIR/binwhen it is not already on PATH, so re-sourcing the rc file in a nested shell stops duplicating that entry too.pvm envnow prints the default-version activation before the wrapper, because the literal snapshot cannot contain the$PVM_DIR/binentry the wrapper adds one line later.2. The bash cd-hook ran on every prompt
_pvm_cd_hookis attached toPROMPT_COMMAND, which bash runs after every command, not just after acd. In a project directory that meant a fork+exec of pvm, an env-file write and anevalon each prompt. Zsh (chpwd) and fish (--on-variable PWD) were already correct — only bash needed the$PWDguard.3.
linux-aarch64was never builtget_target_triple()resolveslinux-aarch64andinstall.shmapsuname -mtopvm-linux-aarch64.tar.gz, but the build matrix only producedlinux-x86_64and the two macOS targets. On ARM Linux both the install script andpvm self-updatefetched an asset that was never uploaded and failed with a 404. Builds natively on theubuntu-24.04-armrunner, so no cross toolchain is needed.Verification
cargo test: 29 unit + 43 integration, all green.cargo fmt --checkandcargo clippy -- -D warningsclean.test_use_does_not_stack_duplicate_path_entriesstarts from a deliberately dirty PATH (active version listed twice plus a stale second version) and asserts the emitted line lists the active version exactly once and drops the stale one.pvm use systemrestores the original PATH, three rc re-sources leave one$PVM_DIR/binentry./opt/tool-*entries — no assertion depends on how the runner lays out its directories.Note for review
The ARM job needs ARM runners available to this repo (
ubuntu-24.04-arm, free for public repos). If that runner is unavailable thebuildjob will fail for that matrix entry only — worth confirming on the first release after merge.Unrelated to this branch, but noticed while reading and worth separate issues: the remote-version cache has no stale fallback (any network failure kills
pvm installeven with a usable index on disk),.php-versionis only read from the current directory with no upward walk, and status messages innetwork.rs/install.rsgo to stdout, which pollutespvm ls-remote | ....Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.