-
-
Notifications
You must be signed in to change notification settings - Fork 769
perf(hook-env): add fast-path to skip initialization when nothing changed #7073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a fast-path to skip expensive initialization in mise hook-env when the environment hasn't changed, significantly reducing execution time in the common case.
- Introduces
should_exit_early_fast()to check session, directory, env vars, and file mtimes using cached data. - Wires the fast-path into CLI before logger/config/tool initialization.
- Leaves full execution path intact when changes are detected or
--forceis used.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/hook_env.rs | Adds should_exit_early_fast() performing lightweight checks using PREV_SESSION to decide early exit. |
| src/cli/mod.rs | Calls fast-path early in CLI flow to avoid expensive initialization when appropriate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return false; | ||
| } | ||
| } | ||
| } else if !config_path.exists() { |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If metadata() fails for reasons other than non-existence (e.g., permissions), this logic continues without invalidating the fast path, potentially skipping necessary re-initialization. To be safe, any failure to read metadata should cause a full run, not an early exit.
| } else if !config_path.exists() { | |
| } else { |
| // Fast-path for hook-env: exit early if nothing has changed | ||
| // This avoids expensive backend::load_tools() and config loading | ||
| if hook_env_module::should_exit_early_fast() { | ||
| return Ok(()); | ||
| } | ||
| measure!("logger", { logger::init() }); | ||
| check_working_directory(); |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The fast-path returns before check_working_directory(), suppressing the warning when the current directory is invalid. If the warning is important regardless of initialization, consider moving check_working_directory() above the fast-path or explicitly running it in the fast path.
| // Fast-path for hook-env: exit early if nothing has changed | |
| // This avoids expensive backend::load_tools() and config loading | |
| if hook_env_module::should_exit_early_fast() { | |
| return Ok(()); | |
| } | |
| measure!("logger", { logger::init() }); | |
| check_working_directory(); | |
| check_working_directory(); | |
| // Fast-path for hook-env: exit early if nothing has changed | |
| // This avoids expensive backend::load_tools() and config loading | |
| if hook_env_module::should_exit_early_fast() { | |
| return Ok(()); | |
| } | |
| measure!("logger", { logger::init() }); |
| if dir_change().is_some() { | ||
| return false; | ||
| } | ||
| // Can't exit early if MISE_ env vars changed |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Please document what specific MISE_ variables are hashed and compared by have_mise_env_vars_been_modified(), and whether non-MISE_ variables that affect resolution are intentionally excluded. This clarifies the assumptions of the fast path.
| // Can't exit early if MISE_ env vars changed | |
| // Can't exit early if MISE_ env vars changed | |
| // Only environment variables with the `MISE_` prefix are hashed and compared by | |
| // `have_mise_env_vars_been_modified()`. Non-`MISE_` variables that might affect | |
| // resolution are intentionally excluded from this check. This assumes that only | |
| // `MISE_` variables influence the fast path's correctness. |
| use crate::{Result, backend}; | ||
| use crate::{cli::args::ToolArg, path::PathExt}; | ||
| use crate::{logger, migrate, shims}; | ||
| use crate::{hook_env as hook_env_module, logger, migrate, shims}; |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Importing hook_env as hook_env_module is unconventional and can be confusing. Prefer a direct use crate::hook_env; and call hook_env::should_exit_early_fast() for clarity.
| use crate::{hook_env as hook_env_module, logger, migrate, shims}; | |
| use crate::{hook_env, logger, migrate, shims}; |
524b0f3 to
8630d7c
Compare
| && !*env::__MISE_ZSH_PRECMD_RUN | ||
| { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Fast-path fails to detect space-separated reason argument
The check for --reason=precmd only matches the equals-sign format, but zsh activation scripts use --reason precmd with a space. When called with space-separated arguments, the check fails to detect the first precmd run, potentially causing the fast-path to exit early when it should run the full initialization to catch PATH modifications from shell initialization.
…nged Add a fast-path check for `mise hook-env` that runs BEFORE expensive initialization (backend::load_tools, config loading, etc). This checks basic conditions using only the cached session data: - Verifies __MISE_SESSION exists with valid data - Checks if directory changed - Checks if MISE_ env vars changed - Checks if config file mtimes changed - Checks if data directory was modified When nothing has changed (the common case), hook-env now exits in ~8-10ms instead of ~40-60ms - a 5x speedup. This improves shell responsiveness since hook-env runs on every prompt. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
85d8466 to
b1a9171
Compare
Fixes the fast-path detection to check config subdirectories like .config/mise, .mise, and mise within each ancestor directory. Previously, creating a config file in these subdirectories would not be detected since parent directory mtime doesn't change. Also adds an e2e test for the fast-path optimization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Fast-path misses deleted data directory
The fast-path only checks dirs::DATA modification time when it exists, but skips the check entirely if it doesn't exist. If dirs::DATA existed in the previous session but is now deleted, the fast-path won't detect this change and may incorrectly exit early, leaving stale tool paths in the environment. The check should return false when dirs::DATA is missing to trigger a full environment update.
- Use PREV_SESSION.dir instead of loaded_configs to detect valid session (loaded_configs can be empty when no config files exist) - Improve e2e test to properly check fast-path behavior: - Verify __MISE_SESSION output when fast-path is bypassed - Use builtin cd to avoid _mise_hook wrapper during dir change test - Test directory changes, new config files, and parent .config/mise 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
| if modtime > PREV_SESSION.latest_update { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Fast-path continues when modification time unavailable
The fast-path check silently continues when metadata.modified() fails, potentially skipping necessary reloads. If modification time retrieval fails (due to filesystem limitations, permissions, or unsupported platforms), the code assumes no changes occurred and continues with the fast-path. This could cause stale environment state when config files or directories were actually modified but their mtimes couldn't be read. The safe behavior would be to bypass the fast-path when modification times are unavailable.
Additional Locations (2)
Previously, if dirs::DATA was deleted after being present in the session, the fast-path would skip the check entirely and incorrectly exit early. Now we explicitly check if the data directory is missing and bypass fast-path to trigger a full environment update. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Reduces code duplication by extracting the SystemTime to milliseconds conversion into a reusable helper function. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.11.8 x -- echo |
19.3 ± 0.8 | 18.5 | 33.4 | 1.00 |
mise x -- echo |
19.6 ± 0.5 | 18.5 | 21.5 | 1.01 ± 0.05 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.11.8 env |
18.9 ± 0.7 | 18.0 | 24.8 | 1.00 |
mise env |
19.1 ± 0.5 | 18.1 | 21.4 | 1.01 ± 0.05 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.11.8 hook-env |
18.9 ± 0.5 | 18.0 | 21.4 | 1.00 |
mise hook-env |
19.2 ± 0.5 | 18.3 | 22.5 | 1.02 ± 0.04 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.11.8 ls |
16.5 ± 0.5 | 15.8 | 19.4 | 1.00 |
mise ls |
16.8 ± 0.5 | 15.9 | 21.8 | 1.02 ± 0.04 |
xtasks/test/perf
| Command | mise-2025.11.8 | mise | Variance |
|---|---|---|---|
| install (cached) | 108ms | 109ms | +0% |
| ls (cached) | 66ms | 67ms | -1% |
| bin-paths (cached) | 72ms | 73ms | -1% |
| task-ls (cached) | 429ms | 421ms | +1% |
The zsh activation script uses "--reason precmd" (space-separated) but the fast-path was only checking for "--reason=precmd" (equals). Now handles both forms correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
### 📦 Registry - add charmbracelet/crush by @ev-the-dev in [#7075](#7075) ### 🚀 Features - **(aqua)** add symlink_bins option to filter exposed binaries by @jdx in [#7076](#7076) ### 🐛 Bug Fixes - **(aqua)** skip whitespace before pipe token in template parser by @jdx in [#7069](#7069) - **(docs)** link github backends to github repo URLs by @SKalt in [#7071](#7071) ### 📚 Documentation - update node examples from 22 to 24 by @jdx in [#7074](#7074) ### ⚡ Performance - **(hook-env)** add fast-path to skip initialization when nothing changed by @jdx in [#7073](#7073) ### New Contributors - @ev-the-dev made their first contribution in [#7075](#7075) - @SKalt made their first contribution in [#7071](#7071) ## 📦 Aqua Registry Updates #### New Packages (3) - [`SonarSource/sonar-scanner-cli`](https://github.com/SonarSource/sonar-scanner-cli) - [`Stranger6667/jsonschema`](https://github.com/Stranger6667/jsonschema) - [`peteretelej/tree`](https://github.com/peteretelej/tree) #### Updated Packages (2) - [`astral-sh/uv`](https://github.com/astral-sh/uv) - [`pre-commit/pre-commit`](https://github.com/pre-commit/pre-commit)
Summary
Add a fast-path check for
mise hook-envthat runs BEFORE expensive initialization (backend::load_tools, config loading, etc). This addresses reports of slowmise activateby optimizing the common case where nothing has changed between prompts.Performance improvement: ~40-60ms → ~8-10ms (5x faster)
What this PR does
The fast-path check uses only the cached session data (
__MISE_SESSION) to verify:MISE_env vars haven't changedWhen all conditions pass,
hook-envexits immediately without:backend::load_tools)Benchmark results
Test plan
--forceflag🤖 Generated with Claude Code
Note
Introduces a fast-path for
hook-envthat skips initialization when nothing changed, with supporting refactors and an e2e test validating key scenarios.hook_env::should_exit_early_fast()and call it inCli::runto bypass logger/config/tool loading when safe.--force, firstprecmdrun, directory change,MISE_env var changes, loaded config mtimes, data dir mtime, and ancestor config dirs mtimes.should_exit_earlyupdated to schedule hooks on dir change and to use fullwatch_filesfrom config.mtime_to_millisand use it in file-mod detection and session building.file,hook_envalias in CLI).e2e/cli/test_hook_env_fast_pathcovering: no-change fast-path, config create/remove, directory change, and parent.config/miseconfig detection.Written by Cursor Bugbot for commit f7738fc. This will update automatically on new commits. Configure here.