fix(hooks): resolve 12 community-reported hooks issues#8058
Conversation
…NFIG_ROOT - Fix error message saying "`run` must be a string" when it should say "`script`" - Replace panic!() with proper error return in Hook::from_toml for invalid values - Make run_one_hook/run_one_hook_with_context return Result for proper error propagation - Warn on preinstall/postinstall hook errors instead of silently ignoring them - Add MISE_CONFIG_ROOT environment variable to hook execution context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the robustness and developer experience of the Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Pull request overview
Improves the hooks system’s reliability by fixing parsing/error-handling behavior, propagating hook execution failures to callers, and enriching hook environments.
Changes:
- Convert hook runners to return
Result<()>, propagate failures, and update call sites to log warnings on hook errors. - Fix hook TOML parsing issues: correct a misleading error message and replace a
panic!()with a user-facing error. - Add
MISE_CONFIG_ROOTto the environment passed to hook scripts.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/toolset/toolset_install.rs | Log warnings when preinstall/postinstall hooks fail instead of silently swallowing errors. |
| src/hooks.rs | Return Result<()> from hook runners, fix parsing errors, remove panic!, and add MISE_CONFIG_ROOT env var. |
| src/cli/install.rs | Log warnings when the postinstall hook fails during mise install. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Code Review
This pull request significantly improves the error handling within the hooks system. Key changes include propagating errors from hook execution instead of silencing them, replacing a panic! with a proper Result in TOML parsing, fixing a misleading error message, and enabling the MISE_CONFIG_ROOT environment variable for hooks. These changes make the hook system more robust and developer-friendly.
My review includes one suggestion to further improve error reporting by collecting all hook failures of a certain type and reporting them together, rather than stopping at the first failure. Overall, this is a solid set of improvements.
…nv vars to shell hooks - Replace manual Hook::from_toml with serde-derived HookDef enum using #[serde(untagged)] to handle string/table/array hook definitions - Fix global hooks being excluded: Config::hooks() filtered out global config hooks because project_root() returns None. Now includes them with config_root() as fallback and marks them as global - Global hooks skip directory matching so they fire for all projects (enter/leave/cd/preinstall/postinstall all work from global config) - Shell hooks now emit environment variable exports (MISE_PROJECT_ROOT, MISE_CONFIG_ROOT, MISE_ORIGINAL_CWD, MISE_PREVIOUS_DIR, MISE_INSTALLED_TOOLS) before the script using the shell's set_env Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… error handling - test_hooks_global: verifies hooks in global config fire for enter/leave/cd/preinstall/postinstall (fixes #5264, #5293, #4875) - test_hooks_shell_env: verifies shell hooks receive MISE_PROJECT_ROOT and MISE_CONFIG_ROOT as exported env vars (fixes #4013, #6054) - test_hooks_error_handling: verifies failing hook scripts warn instead of crashing, and don't block subsequent hooks or installation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ting Address PR review feedback: the early return `?` in run_one_hook_with_context would skip remaining hooks after the first failure. Changed to warn with contextual error message (hook type, root directory) and continue processing remaining hooks. Also fixed e2e test to capture output once for multiple assertions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix #4583: Leave hooks now fire before Enter hooks when changing directories (was Enter before Leave). Scheduling order changed in hook_env to Leave → Cd → Enter. - Fix #7183: Non-global pre/postinstall hooks now only fire when CWD is under the hook's config root, preventing project hooks from running during `mise use -g` from an unrelated directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The postinstall hook was using the installing toolset (`self`), which may be a filtered subset when installing specific tools. Now uses `config.get_toolset()` after config reset so all installed tools (including previously installed ones) are available on PATH when the hook runs. Fixes #6323, #7489. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fc34db9 to
9e0b4dc
Compare
- Fix #6162: Preinstall hooks now use `full_env_without_tools()` which skips `tools=true` env directive resolution. During preinstall, tools aren't installed yet so tool-dependent env vars (e.g. `{{env.ANDROID_HOME}}`) can't be resolved. - Fix #5481: PowerShell `_mise_hook` now checks for non-empty output before calling Invoke-Expression, preventing empty string errors from polluting the $Error variable on every prompt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hooks from old configs (previous session, no longer active) were parsed via config_file::parse which always sets global: false. Now checks project_root() and marks them as global with the correct config root, matching the behavior in Config::hooks(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.7 x -- echo |
23.9 ± 1.1 | 21.5 | 27.7 | 1.00 |
mise x -- echo |
25.4 ± 1.3 | 22.6 | 30.7 | 1.06 ± 0.08 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.7 env |
22.7 ± 0.7 | 20.6 | 25.5 | 1.00 |
mise env |
24.0 ± 1.0 | 21.6 | 27.1 | 1.06 ± 0.06 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.7 hook-env |
23.5 ± 0.9 | 21.3 | 26.8 | 1.00 |
mise hook-env |
24.9 ± 1.2 | 22.8 | 28.6 | 1.06 ± 0.06 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.7 ls |
21.6 ± 0.8 | 19.6 | 24.3 | 1.00 |
mise ls |
22.7 ± 1.0 | 20.4 | 26.6 | 1.05 ± 0.06 |
xtasks/test/perf
| Command | mise-2026.2.7 | mise | Variance |
|---|---|---|---|
| install (cached) | 127ms | 127ms | +0% |
| ls (cached) | 77ms | 78ms | -1% |
| bin-paths (cached) | 82ms | 83ms | -1% |
| task-ls (cached) | 588ms | 594ms | -1% |
run_one_hook and run_one_hook_with_context handle all errors internally with warn!, so they always return Ok(()). Change return type to () and remove unreachable if-let-Err blocks at call sites. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
config.get_toolset().await? could propagate a resolution error after all tools were successfully installed, skipping both the postinstall hook and the progress bar footer. Fall back to the current toolset if resolution fails, matching the defensive pattern used just above for self.resolve(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
### 🚀 Features - **(node)** support package.json as idiomatic version file by @jdx in [#8059](#8059) - **(ruby)** graduate precompiled ruby from experimental (gradual rollout) by @jdx in [#8052](#8052) - add --dry-run-code flag to exit non-zero when there is work to do by @jdx in [#8063](#8063) ### 🐛 Bug Fixes - **(core)** respect MISE_ARCH override in bun and erlang plugins by @jdx in [#8062](#8062) - **(hooks)** resolve 12 community-reported hooks issues by @jdx in [#8058](#8058) - accept key=value format in set/add subcommands by @jdx in [#8053](#8053) ### 📚 Documentation - bump action versions in GitHub Actions examples by @muzimuzhi in [#8065](#8065) - add opengraph meta tags by @jdx in [#8066](#8066) ### 📦️ Dependency Updates - upgrade toml to 0.9 and toml_edit to 0.24 (TOML 1.1) by @jdx in [#8057](#8057) ### 📦 Registry - add quicktype (npm:quicktype) by @zdunecki in [#8054](#8054) - use inline table for test definitions by @jdx in [#8056](#8056)
## Summary Addresses 12 community-reported issues with the hooks system, fixing the most commonly encountered problems with global hooks, hook execution order, error handling, and shell integration. ## What's fixed ### Global hooks now work (jdx#5264, jdx#5293, jdx#4875) Hooks defined in `~/.config/mise/config.toml` were silently ignored. Global hooks now fire correctly for all hook types (enter, leave, cd, preinstall, postinstall) across all projects. ### Hook execution order corrected (jdx#4583) When changing from directory A to directory B, leave hooks now fire before enter hooks. Previously the order was reversed. ### Hooks no longer loop infinitely in fish (jdx#4378) Running `mise run` from inside a hook (e.g. `enter = "mise run setup"`) could cause infinite recursion when using fish shell, because fish sources its config on subshell startup which re-activates mise. Hooks now set `MISE_NO_HOOKS=1` in their subprocess environment to prevent this. ### `mise use -g` no longer triggers project hooks (jdx#7183) Running `mise use -g` from a project directory incorrectly fired that project's preinstall/postinstall hooks. Non-global hooks now only fire when you're inside the project that defines them. ### Postinstall hooks can find all installed tools (jdx#6323, jdx#7489) Postinstall hooks now run with the full resolved toolset on PATH, so commands from previously-installed tools (like `lefthook`, `pnpm`, etc.) are available. ### `tools=true` env vars no longer evaluated during preinstall (jdx#6162) Environment variables with `tools = true` (e.g. `AN_ENV_VAR = { value = "{{env.ANDROID_HOME}}", tools = true }`) were being resolved during preinstall hooks, before the tools providing those variables were installed. Preinstall hooks now skip tool-dependent env var resolution. ### Shell hooks receive environment variables (jdx#4013, jdx#6054) Shell hooks (with `shell = "bash"` etc.) now receive `MISE_PROJECT_ROOT`, `MISE_CONFIG_ROOT`, `MISE_ORIGINAL_CWD`, `MISE_PREVIOUS_DIR`, and `MISE_INSTALLED_TOOLS` as exported variables. ### PowerShell `$Error` no longer polluted (jdx#5481) The `_mise_hook` function in PowerShell was adding errors to `$Error` on every prompt because it called `Invoke-Expression` with empty output. Now checks for non-empty output first. ### Hook errors no longer crash or get swallowed - Failing hooks warn with context (hook type, config directory, error) and continue running remaining hooks - Invalid hook definitions produce proper errors instead of panics - `MISE_CONFIG_ROOT` is now available in hook environments ### Serde-based hook parsing Hook definitions now use serde deserialization instead of manual TOML parsing, giving better error messages for malformed configs. ## Not actionable (investigated) - jdx#5001 — Already fixed in a prior release - jdx#6347 — Likely fixed by prior `starts_with` direction fix - jdx#7792 — By design (`hook-env` runs on prompt, can't update shell mid-command) - jdx#7255 — By design (use `MISE_INSTALLED_TOOLS` JSON or tool-level `[tools.X.postinstall]`) - jdx#6581 — Not a bug (mise inherits local env vars) ## Test plan - [x] All linters pass - [x] 447 unit tests pass - [x] All 10 hooks e2e tests pass including 3 new ones (global hooks, shell env vars, error handling) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches core hook execution and environment construction paths (including install-time hooks and shell integrations), so regressions could affect when/where hooks fire or what env they see across projects and shells. > > **Overview** > Fixes hook execution across config scopes by **treating hooks from global configs as truly global** (skipping per-project directory matching) while tightening non-global pre/postinstall hooks to only run when the current working directory is under that config’s root. > > Refactors hook parsing to a serde-driven `HookDef` (string/table/array) and removes panic paths; hook failures now **warn with hook type + root and continue** rather than crashing or silently swallowing errors. Shell hook output now includes exported `MISE_PROJECT_ROOT`/`MISE_CONFIG_ROOT` (plus other hook context), hook subprocesses set `MISE_NO_HOOKS=1` to avoid recursion, and preinstall hooks run with an env that skips `tools=true` directives. > > Improves install-time behavior by running `postinstall` hooks with a fully resolved toolset on `PATH`, adjusts hook scheduling order on dir changes, and tweaks PowerShell activation to only `Invoke-Expression` when `hook-env` returns non-empty output. Adds new e2e coverage for global hooks, shell env exports, and error handling. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cf3ab75. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### 🚀 Features - **(node)** support package.json as idiomatic version file by @jdx in [jdx#8059](jdx#8059) - **(ruby)** graduate precompiled ruby from experimental (gradual rollout) by @jdx in [jdx#8052](jdx#8052) - add --dry-run-code flag to exit non-zero when there is work to do by @jdx in [jdx#8063](jdx#8063) ### 🐛 Bug Fixes - **(core)** respect MISE_ARCH override in bun and erlang plugins by @jdx in [jdx#8062](jdx#8062) - **(hooks)** resolve 12 community-reported hooks issues by @jdx in [jdx#8058](jdx#8058) - accept key=value format in set/add subcommands by @jdx in [jdx#8053](jdx#8053) ### 📚 Documentation - bump action versions in GitHub Actions examples by @muzimuzhi in [jdx#8065](jdx#8065) - add opengraph meta tags by @jdx in [jdx#8066](jdx#8066) ### 📦️ Dependency Updates - upgrade toml to 0.9 and toml_edit to 0.24 (TOML 1.1) by @jdx in [jdx#8057](jdx#8057) ### 📦 Registry - add quicktype (npm:quicktype) by @zdunecki in [jdx#8054](jdx#8054) - use inline table for test definitions by @jdx in [jdx#8056](jdx#8056)
### 🚀 Features - **(node)** support package.json as idiomatic version file by @jdx in [jdx#8059](jdx#8059) - **(ruby)** graduate precompiled ruby from experimental (gradual rollout) by @jdx in [jdx#8052](jdx#8052) - add --dry-run-code flag to exit non-zero when there is work to do by @jdx in [jdx#8063](jdx#8063) ### 🐛 Bug Fixes - **(core)** respect MISE_ARCH override in bun and erlang plugins by @jdx in [jdx#8062](jdx#8062) - **(hooks)** resolve 12 community-reported hooks issues by @jdx in [jdx#8058](jdx#8058) - accept key=value format in set/add subcommands by @jdx in [jdx#8053](jdx#8053) ### 📚 Documentation - bump action versions in GitHub Actions examples by @muzimuzhi in [jdx#8065](jdx#8065) - add opengraph meta tags by @jdx in [jdx#8066](jdx#8066) ### 📦️ Dependency Updates - upgrade toml to 0.9 and toml_edit to 0.24 (TOML 1.1) by @jdx in [jdx#8057](jdx#8057) ### 📦 Registry - add quicktype (npm:quicktype) by @zdunecki in [jdx#8054](jdx#8054) - use inline table for test definitions by @jdx in [jdx#8056](jdx#8056)
Summary
Addresses 12 community-reported issues with the hooks system, fixing the most commonly encountered problems with global hooks, hook execution order, error handling, and shell integration.
What's fixed
Global hooks now work (#5264, #5293, #4875)
Hooks defined in
~/.config/mise/config.tomlwere silently ignored. Global hooks now fire correctly for all hook types (enter, leave, cd, preinstall, postinstall) across all projects.Hook execution order corrected (#4583)
When changing from directory A to directory B, leave hooks now fire before enter hooks. Previously the order was reversed.
Hooks no longer loop infinitely in fish (#4378)
Running
mise runfrom inside a hook (e.g.enter = "mise run setup") could cause infinite recursion when using fish shell, because fish sources its config on subshell startup which re-activates mise. Hooks now setMISE_NO_HOOKS=1in their subprocess environment to prevent this.mise use -gno longer triggers project hooks (#7183)Running
mise use -gfrom a project directory incorrectly fired that project's preinstall/postinstall hooks. Non-global hooks now only fire when you're inside the project that defines them.Postinstall hooks can find all installed tools (#6323, #7489)
Postinstall hooks now run with the full resolved toolset on PATH, so commands from previously-installed tools (like
lefthook,pnpm, etc.) are available.tools=trueenv vars no longer evaluated during preinstall (#6162)Environment variables with
tools = true(e.g.AN_ENV_VAR = { value = "{{env.ANDROID_HOME}}", tools = true }) were being resolved during preinstall hooks, before the tools providing those variables were installed. Preinstall hooks now skip tool-dependent env var resolution.Shell hooks receive environment variables (#4013, #6054)
Shell hooks (with
shell = "bash"etc.) now receiveMISE_PROJECT_ROOT,MISE_CONFIG_ROOT,MISE_ORIGINAL_CWD,MISE_PREVIOUS_DIR, andMISE_INSTALLED_TOOLSas exported variables.PowerShell
$Errorno longer polluted (#5481)The
_mise_hookfunction in PowerShell was adding errors to$Erroron every prompt because it calledInvoke-Expressionwith empty output. Now checks for non-empty output first.Hook errors no longer crash or get swallowed
MISE_CONFIG_ROOTis now available in hook environmentsSerde-based hook parsing
Hook definitions now use serde deserialization instead of manual TOML parsing, giving better error messages for malformed configs.
Not actionable (investigated)
starts_withdirection fixhook-envruns on prompt, can't update shell mid-command)MISE_INSTALLED_TOOLSJSON or tool-level[tools.X.postinstall])Test plan
🤖 Generated with Claude Code
Note
Medium Risk
Touches core hook execution and environment construction paths (including install-time hooks and shell integrations), so regressions could affect when/where hooks fire or what env they see across projects and shells.
Overview
Fixes hook execution across config scopes by treating hooks from global configs as truly global (skipping per-project directory matching) while tightening non-global pre/postinstall hooks to only run when the current working directory is under that config’s root.
Refactors hook parsing to a serde-driven
HookDef(string/table/array) and removes panic paths; hook failures now warn with hook type + root and continue rather than crashing or silently swallowing errors. Shell hook output now includes exportedMISE_PROJECT_ROOT/MISE_CONFIG_ROOT(plus other hook context), hook subprocesses setMISE_NO_HOOKS=1to avoid recursion, and preinstall hooks run with an env that skipstools=truedirectives.Improves install-time behavior by running
postinstallhooks with a fully resolved toolset onPATH, adjusts hook scheduling order on dir changes, and tweaks PowerShell activation to onlyInvoke-Expressionwhenhook-envreturns non-empty output. Adds new e2e coverage for global hooks, shell env exports, and error handling.Written by Cursor Bugbot for commit cf3ab75. This will update automatically on new commits. Configure here.