Repository navigation
Fix fish prompt hook never skipping redundant activate calls - #528
ethanleifer wants to merge 1 commit into
Conversation
The fish hook compared _PYENV_VH_MTIMES against "(stat ...)". Inside double quotes fish does not run command substitution, so the comparison was against literal text and always failed. The hook then ran `pyenv activate --quiet` before every prompt instead of only when the directory or a version file changed. Run stat into a local list and compare it quoted, the same way _PYENV_VH_MTIMES is stored. stat still only runs when $PWD is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="test/init.bats">
<violation number="1" location="test/init.bats:117">
P1: This comparison still fails whenever `stat` emits more than one line, which is the common case: `_PYENV_VH_PATHS` holds one entry per directory level between PWD and `/` (plus `${PYENV_ROOT}/version`) whenever the `.python-version` is not directly in PWD, and `stat -c %Y`/`-f %m` prints one line per file. `set -l mtimes (stat ...)` then creates a multi-element list, and `"$mtimes" = "$_PYENV_VH_MTIMES"` expands to >3 arguments, so fish `test` fails with "too many arguments" (status 2): the `if` is false, stderr is spammed on every prompt, and `pyenv activate --quiet` still runs on every prompt — the exact bug this PR is meant to fix. Collapse each list to a single string before comparing, e.g. `if test (string join " " $mtimes) = (string join " " $_PYENV_VH_MTIMES)` (mtime elements are pure digits, so the delimiter is safe, and `string` is already a requirement of this generated hook via `string replace`).</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return \$ret | ||
| if test "\$PWD" = "\$_PYENV_VH_PWD" | ||
| set -l mtimes (stat ${_stat_fmt} \$_PYENV_VH_PATHS 2>/dev/null) | ||
| if test "\$mtimes" = "\$_PYENV_VH_MTIMES" |
There was a problem hiding this comment.
P1: This comparison still fails whenever stat emits more than one line, which is the common case: _PYENV_VH_PATHS holds one entry per directory level between PWD and / (plus ${PYENV_ROOT}/version) whenever the .python-version is not directly in PWD, and stat -c %Y/-f %m prints one line per file. set -l mtimes (stat ...) then creates a multi-element list, and "$mtimes" = "$_PYENV_VH_MTIMES" expands to >3 arguments, so fish test fails with "too many arguments" (status 2): the if is false, stderr is spammed on every prompt, and pyenv activate --quiet still runs on every prompt — the exact bug this PR is meant to fix. Collapse each list to a single string before comparing, e.g. if test (string join " " $mtimes) = (string join " " $_PYENV_VH_MTIMES) (mtime elements are pure digits, so the delimiter is safe, and string is already a requirement of this generated hook via string replace).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At test/init.bats, line 117:
<comment>This comparison still fails whenever `stat` emits more than one line, which is the common case: `_PYENV_VH_PATHS` holds one entry per directory level between PWD and `/` (plus `${PYENV_ROOT}/version`) whenever the `.python-version` is not directly in PWD, and `stat -c %Y`/`-f %m` prints one line per file. `set -l mtimes (stat ...)` then creates a multi-element list, and `"$mtimes" = "$_PYENV_VH_MTIMES"` expands to >3 arguments, so fish `test` fails with "too many arguments" (status 2): the `if` is false, stderr is spammed on every prompt, and `pyenv activate --quiet` still runs on every prompt — the exact bug this PR is meant to fix. Collapse each list to a single string before comparing, e.g. `if test (string join " " $mtimes) = (string join " " $_PYENV_VH_MTIMES)` (mtime elements are pure digits, so the delimiter is safe, and `string` is already a requirement of this generated hook via `string replace`).</comment>
<file context>
@@ -112,9 +112,11 @@ function _pyenv_virtualenv_hook --on-event fish_prompt;
- return \$ret
+ if test "\$PWD" = "\$_PYENV_VH_PWD"
+ set -l mtimes (stat ${_stat_fmt} \$_PYENV_VH_PATHS 2>/dev/null)
+ if test "\$mtimes" = "\$_PYENV_VH_MTIMES"
+ return \$ret
+ end
</file context>
| if test "\$mtimes" = "\$_PYENV_VH_MTIMES" | |
| if test (string join " " $mtimes) = (string join " " $_PYENV_VH_MTIMES) |
In fish, the prompt hook ran
pyenv activate --quietbefore every prompt. The cache from #523 is supposed to skip that call when nothing has changed, but its fish version never matched.Cause
The fish hook compared the stored mtimes against a quoted string:
Fish does not run command substitution inside double quotes, so this compared
_PYENV_VH_MTIMESagainst the literal text(stat -L -f %m ...). The comparison was always false. The bash and zsh hooks use"$(stat ...)", which does run, so only fish was affected.Fix
Run
statinto a local list, then compare it quoted:_PYENV_VH_MTIMESis stored as a list from(stat ...). Quoting both lists joins them with spaces the same way, so they compare correctly with one or many paths.I avoided
"$(...)"for two reasons. It needs fish 3.4 or later, and it keeps newlines, so it wouldn't match the stored list. As before,statonly runs when$PWDis unchanged.Testing
test/init.bats"outputs fish-specific syntax" is updated for the new output. It fails against the old hook code.pyenvthat countsactivatecalls:cd .., then another prompt.python-versionin a parent, then another prompt.python-versionPYENV_VERSIONsetWith the fix, fish behaves like the bash and zsh hooks. It reruns
activateafter a directory change, a version file change, or aPYENV_VERSIONchange, and skips it otherwise. On my machine, each skipped call saves about 250–290 ms per prompt.🤖 Generated with Claude Code
Summary by cubic
Fixes the fish prompt hook so it no longer runs
pyenv activate --quietbefore every prompt, restoring the mtime-based deduplication that already worked for bash and zsh.Bug Fixes
_PYENV_VH_MTIMESagainst the literal text and never matched.statnow runs into a local list and compares quoted, matching how_PYENV_VH_MTIMESis stored, and still only runs when$PWDis unchanged.test/init.batsis updated to match.Written for commit 820457b. Summary will update on new commits.