Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces two new settings to optimize mise hook-env performance on slow filesystems like NFS by reducing expensive stat operations. The changes allow users to cache directory checks and limit hook-env execution to directory changes only.
Key changes:
- Adds
hook_env.cache_ttlsetting to cache directory check results for a configurable duration - Adds
hook_env.chpwd_onlysetting to skip hook-env checks on shell prompts when the directory hasn't changed - Implements caching logic to track directories without config files and reuse this information within the TTL window
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/hook_env.rs | Implements the caching logic and chpwd_only check in should_exit_early_fast(), adds fields to HookEnvSession to track cached state |
| settings.toml | Defines the two new settings with documentation explaining their purpose and trade-offs |
| schema/mise.json | Updates the JSON schema to include the new hook_env settings object |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let cache_ttl_ms = duration::parse_duration(&settings.hook_env.cache_ttl) | ||
| .map(|d| d.as_millis()) | ||
| .unwrap_or(0); |
There was a problem hiding this comment.
The unwrap_or(0) silently ignores parsing errors for cache_ttl. If a user provides an invalid duration format, they won't receive any feedback. Consider logging a warning when parsing fails so users can identify configuration issues.
| { | ||
| let config_subdirs = DEFAULT_CONFIG_FILENAMES | ||
| .iter() | ||
| .map(|f| f.rsplit_once("/").map(|(dir, _)| dir).unwrap_or("")) |
There was a problem hiding this comment.
Using string literal / for path separation is not cross-platform. Consider using std::path::MAIN_SEPARATOR or path methods to ensure Windows compatibility.
| .map(|f| f.rsplit_once("/").map(|(dir, _)| dir).unwrap_or("")) | |
| .map(|f| { | |
| Path::new(f) | |
| .parent() | |
| .map(|p| p.to_str().unwrap_or("")) | |
| .unwrap_or("") | |
| }) |
…r NFS optimization Add two new settings to mitigate shell latency on slow filesystems like NFS: - `hook_env.cache_ttl` (default: "0s"): Cache directory check results for this duration, skipping stat operations within the TTL window - `hook_env.chpwd_only` (default: false): Only run hook-env checks on directory change, not on every shell prompt These settings address the performance issues described in #2164 where users on NFS experience multi-second delays due to many stat operations during hook-env execution. Usage: export MISE_HOOK_ENV_CACHE_TTL=5s export MISE_HOOK_ENV_CHPWD_ONLY=1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ae94d35 to
17c8adf
Compare
|
| Command | mise-2025.12.7 | mise | Variance |
|---|---|---|---|
| install (cached) | 113ms | 110ms | +2% |
| ls (cached) | 66ms | 67ms | -1% |
| bin-paths (cached) | 73ms | 72ms | +1% |
| task-ls (cached) | 451ms | ✅ 280ms | +61% |
✅ Performance improvement: task-ls cached is 61%
- Fix cache directory skip logic to only trust cache when within TTL window (previously used stale cache after TTL expired, missing new config files) - Move env var check before chpwd_only early exit since it's a cheap in-memory hash comparison with no filesystem I/O - Log warning when hook_env.cache_ttl has invalid duration format instead of silently falling back to 0 - Use cross-platform Path methods instead of string literal "/" for parsing config subdirectories 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The directory caching optimization was dead code because: 1. When within_ttl_window is true, we return early (line 134) 2. When we reach the cache check (line 178), within_ttl_window is always false 3. The condition `if within_ttl_window && ...` is therefore always false Since the cache is only meant to be used within the TTL window, and we skip all filesystem checks during that window anyway, the cache adds overhead without providing any benefit. Removed: - checked_dirs_without_config field from HookEnvSession - Dead cache skip logic in should_exit_early_fast() - Expensive computation in build_session() that populated the cache 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Bug: TTL cache not refreshed after filesystem checks pass
The cache_ttl feature doesn't work as intended because last_full_check is only updated in build_session(), which is only called when config actually changes. When the TTL expires and should_exit_early_fast() performs filesystem checks that find nothing changed (returning true at line 187), last_full_check is never updated. Subsequent prompts will repeatedly perform expensive stat operations since the original TTL has long expired. The cache effectively only works for one TTL period after each config change, defeating the NFS optimization purpose.
src/hook_env.rs#L131-L187
Lines 131 to 187 in 263f46c
src/hook_env.rs#L335-L336
Lines 335 to 336 in 263f46c
The cache_ttl feature wasn't working correctly because last_full_check was only updated in build_session() which is only called when config changes. When TTL expired and filesystem checks passed, the timestamp wasn't updated, causing subsequent prompts to repeat expensive stat operations. Fix by storing the last check timestamp in per-directory files under ~/.local/state/mise/hook-env-checks/<hash>. This allows updating the timestamp when exiting early after filesystem checks pass, without needing to output shell commands. Per-directory files (using CWD hash) ensure multiple shells in different directories don't interfere with each other. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Avoid unnecessary file I/O when the cache_ttl feature is not being used (the default). The timestamp file read/write is now conditional on cache_ttl_ms > 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
### 🚀 Features - **(conda)** add dependency resolution for conda packages by @jdx in [#7280](#7280) - **(go)** add created_at support to ls-remote --json by @jdx in [#7305](#7305) - **(hook-env)** add hook_env.cache_ttl and hook_env.chpwd_only settings for NFS optimization by @jdx in [#7312](#7312) - **(hooks)** add MISE_TOOL_NAME and MISE_TOOL_VERSION to preinstall/postinstall hooks by @jdx in [#7311](#7311) - **(shell_alias)** add shell_alias support for cross-shell aliases by @jdx in [#7316](#7316) - **(tool)** add security field to mise tool --json by @jdx in [#7303](#7303) - add --before flag for date-based version filtering by @jdx in [#7298](#7298) ### 🐛 Bug Fixes - **(aqua)** support cosign v3 bundle verification by @jdx in [#7314](#7314) - **(config)** use correct config_root in tera context for hooks by @jdx in [#7309](#7309) - **(nu)** fix nushell deactivation script on Windows by @fu050409 in [#7213](#7213) - **(python)** apply uv_venv_create_args in auto-venv code path by @jdx in [#7310](#7310) - **(shell)** escape exe path in activation scripts for paths with spaces by @jdx in [#7315](#7315) - **(task)** parallelize exec_env loading to fix parallel task execution by @jdx in [#7313](#7313) - track downloads for python and java by @jdx in [#7304](#7304) - include full tool ID in download track by @jdx in [#7320](#7320) ### 📚 Documentation - Switch `postinstall` code to be shell-agnostic by @thejcannon in [#7317](#7317) ### 🧪 Testing - **(e2e)** disable debug mode by default for windows-e2e by @jdx in [#7318](#7318) ### New Contributors - @fu050409 made their first contribution in [#7213](#7213)
Summary
Adds two new settings to mitigate shell latency on slow filesystems like NFS (addresses #2164):
hook_env.cache_ttl(default:"0s"): Cache directory check results for this duration, skipping stat operations within the TTL windowhook_env.chpwd_only(default:false): Only run hook-env checks on directory change, not on every shell promptThe Problem
On NFS filesystems with cold cache,
mise hook-envcauses multi-second delays because each stat operation can take hundreds of milliseconds. Theshould_exit_early_fast()function performs:.mise/,.config/mise/, etc.)For a path 20 levels deep: ~80+ stats = potential 17+ second delay on NFS.
The Solution
These settings allow users to trade off accuracy for performance:
Trade-offs
cache_ttlenabled, newly created config files may not be detected until the TTL expireschpwd_onlyenabled, changes to existing config files won't be detected until directory changemise hook-env --forceTest plan
mise settings get hook_env.cache_ttland env vars🤖 Generated with Claude Code
Note
Introduce hook-env performance settings (
hook_env.cache_ttl,hook_env.chpwd_only) and implement per-directory timestamp caching to skip costly filesystem checks.should_exit_early_fast(); skip filesystem stats when within TTL or on precmd withchpwd_only.state/hook-env-checks; read/write viaread_last_full_check/write_last_full_checkand update inbuild_session().Path::parent().settings.hook_env.cache_ttl(Duration,MISE_HOOK_ENV_CACHE_TTL) andsettings.hook_env.chpwd_only(Bool,MISE_HOOK_ENV_CHPWD_ONLY) toschema/mise.jsonandsettings.tomlwith docs.Written by Cursor Bugbot for commit 8679af0. This will update automatically on new commits. Configure here.