`~/.vite-plus/env` costs 85-315 ms per shell start on long PATHs (super-linear `${var#*pattern}` in the dedupe loop)
Maintainers usually reply within 1 day
@jonatankruszewski is already working on this.
Since Oct 6, 2026.
- #2905 by @jonatankruszewski — open
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 82/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Domain
- cli, developer-experience, performance, tooling
Research direction
The POSIX env template is ENV_TEMPLATE_POSIX in crates/vp_global_cli/src/commands/env/setup.rs. Replace the ${__vp_tmp#*":${__vp_dir}:"} dedupe loop with the IFS-split rebuild given in the issue so PATH stays linear on zsh, bash, and dash. Slot the rewrite into the existing snapshot runner from #2494 (sh, bash, zsh) and confirm the emitted ~/.vite-plus/env still produces the same PATH.
Written by the indexing model from the issue text.
Description
Summary
The PATH dedupe loop in the POSIX env script (ENV_TEMPLATE_POSIX in crates/vp_global_cli/src/commands/env/setup.rs, introduced by #2494, shipped as ~/.vite-plus/env) runs
__vp_after="${__vp_tmp#*":${__vp_dir}:"}"
once per occurrence of each vp dir in PATH. ${var#*X} (shortest-prefix removal with a leading *) is super-linear in the offset of X on every POSIX shell I tested. With a PATH of ~8 KB (147 entries, which is what mise activate produces for a user with ~110 globally installed tools) each expansion takes 50-260 ms depending on the shell, and the loop runs twice (bin and fallback-bin).
Because the installer puts . "$HOME/.vite-plus/env" into ~/.zshenv, this is paid by every zsh process on the machine, interactive or not: zsh -c true goes from 0 ms to 180 ms. Every #!/usr/bin/env zsh script, every zsh -c from a tool, every IDE env probe pays it.
Environment: vp v1.0.0, macOS 26 arm64, zsh 5.9 (/bin/zsh) and 5.9.2 (brew), bash 5.3.15 and 3.2, dash.
Repro (zsh, 3 lines)
PATH="$(printf '/Users/me/.local/share/mise/installs/tool-%03d/latest/bin:' {1..140})/x/bin"; tmp=":${PATH}:"; d=/x/bin
typeset -F3 SECONDS=0; a="${tmp#*":${d}:"}"; print "#* : ${SECONDS}s"
SECONDS=0; b="${tmp%%":${d}:"*}"; print "%%* : ${SECONDS}s"
#* : 0.109s
%%* : 0.000s
Measurements
Single ${tmp#*":${dir}:"} on an 8.7 KB PATH, target dir last:
| shell | #* (shipped) |
%% |
|---|---|---|
| zsh 5.9 | 147 ms | 5 ms |
| zsh 5.9.2 | 146 ms | 5 ms |
| bash 5.3.15 | 63 ms | 14 ms |
| bash 3.2 | 53 ms | 13 ms |
| dash | 264 ms | 4 ms |
Cost of sourcing the whole env file in zsh -f as PATH grows (vp dirs present, so the loop runs):
| PATH entries | chars | time |
|---|---|---|
| 20 | 1353 | 4 ms |
| 40 | 2653 | 13 ms |
| 80 | 5253 | 51 ms |
| 147 | 9656 | 172 ms |
| 300 | 19754 | 720 ms |
Whole env file from the real 147-entry PATH, median of 5:
| file | zsh 5.9 | zsh 5.9.2 | bash 5.3 | bash 3.2 | dash |
|---|---|---|---|---|---|
shipped ~/.vite-plus/env |
177 | 178 | 85 | 83 | 315 |
| literal-prefix one-liner (see below) | 6 | 6 | 128 | 133 | 147 |
| IFS-split loop (proposed) | 6 | 6 | 9 | 13 | 5 |
Proposed fix
A literal-prefix strip (__vp_after="${__vp_tmp#"${__vp_before}:${__vp_dir}:"}") fixes zsh but makes bash and dash slower (table above). Splitting PATH on : once and rebuilding it is linear on every shell and produced an identical PATH to the current script in /bin/sh, dash, bash 3.2, bash 5.3 and zsh for a PATH containing both vp dirs, one of them twice:
__vp_bin="$HOME/.vite-plus/bin"
__vp_fallback="$HOME/.vite-plus/fallback-bin"
__vp_new=
__vp_ifs=$IFS
IFS=:
if [ -n "${ZSH_VERSION-}" ]; then setopt localoptions shwordsplit noglob; else set -f; fi
for __vp_p in $PATH; do
case "$__vp_p" in "$__vp_bin"|"$__vp_fallback") ;; *) __vp_new="${__vp_new:+${__vp_new}:}${__vp_p}" ;; esac
done
IFS=$__vp_ifs
[ -n "${ZSH_VERSION-}" ] || set +f
PATH="${__vp_bin}${__vp_new:+:${__vp_new}}:${__vp_fallback}"
export PATH
unset __vp_bin __vp_fallback __vp_new __vp_ifs __vp_p
The snapshot runner from #2494 already exercises the file in sh, bash and zsh, so the rewrite can slot into the same tests.
Who hits this
Anyone whose PATH is long when a nested shell starts: mise/asdf/nix users with many tools, monorepo devs with per-package bin dirs, CI images. A fresh login shell with a 30-entry PATH pays ~4 ms, which is why it is easy to miss.
- Dominant language
- Rust
- Stars
- 6k
- Forks
- 271
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 172
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from voidzero-dev/vite-plus
-
pending triage
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
voidzero-dev/vite-plus#2892 · 1 reaction ·
Maintainers usually reply within 1 day
-
pending triage
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
voidzero-dev/vite-plus#2882 · 2 comments ·
Maintainers usually reply within 1 day
-
documentation pending triage
Difficulty 1/5 Under an hour Newbie friendliness 78/100
voidzero-dev/vite-plus#2875 · 4 comments ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
voidzero-dev/vite-plus#2849 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
voidzero-dev/vite-plus#2097 · 10 comments · 2 reactions ·
Maintainers usually reply within 1 day
All issues in voidzero-dev/vite-plus
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 5 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
tauri-apps/tauri#16219 ·
Maintainers usually reply within 2 days
-
state:triage-needed
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
ktuner keeps a stale ledger path and can never restore that entryPossibly taken @Frun1na claimed this today. Opencomponent:ktuner
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
agentic-os-org/ANOLISA#6483 · 1 comment ·
Maintainers usually reply within 1 day