fix(hook-env): skip remote version fetching for uninstalled tools in prefer-offline mode#7976
fix(hook-env): skip remote version fetching for uninstalled tools in prefer-offline mode#7976
Conversation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a user types an unknown tool name (e.g. `mise use foo`), instead of just failing with "not found in registry", suggest similar tools from both the mise registry and aqua registry using Jaro-Winkler string similarity. - Bump xx to 2.5 for the suggest module - Add package_ids() to aqua-registry crate for querying available tools - Add aqua_suggest() for fuzzy-matching aqua package names - Replace plain error in backend_arg with "Did you mean?" suggestions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When running `mise install <tool>` for a tool not in any mise.toml or .tool-versions file, show a warning suggesting `mise use` instead. Also adds e2e tests for both the "Did you mean?" and inactive tool features. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use HashMap for O(1) lookup in aqua_suggest instead of nested loops - Deduplicate suggestions: skip aqua tools whose name matches a mise suggestion - Skip inactive tool warning in dry-run mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…prefer-offline mode When a tool version is specified but not installed locally, resolve_version() would make an HTTP request to validate the version exists remotely. In prefer-offline mode (hook-env, activate, exec, etc.), this adds ~370ms of latency to every shell prompt. Skip the remote fetch and accept the version string as-is — it will be validated when the user runs `mise install`. Co-Authored-By: Claude Opus 4.5 <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 primarily focuses on optimizing 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
This PR optimizes shell prompt performance by skipping remote version fetching for uninstalled tools when running in prefer-offline mode (e.g., hook-env, activate). Additionally, it improves the user experience with better error messages and warnings.
Changes:
- Skip HTTP requests to validate remote versions for uninstalled tools in prefer-offline mode
- Add "Did you mean?" suggestions when tools are not found in the registry
- Warn users when installing tools that aren't activated in any config file
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/toolset/tool_version.rs | Early return in resolve_version() when PREFER_OFFLINE is true to skip remote version fetching |
| src/cli/install.rs | Track and warn about installed tools not present in config files |
| src/cli/args/backend_arg.rs | Add "Did you mean?" suggestions for unknown tools using both mise and aqua registries |
| src/aqua/aqua_registry_wrapper.rs | Implement aqua_suggest() function to find similar tool names in aqua registry |
| crates/aqua-registry/src/registry.rs | Add package_ids() helper to expose all aqua package IDs |
| crates/aqua-registry/src/lib.rs | Export package_ids function |
| e2e/cli/test_install_inactive_hint | Add test for inactive tool installation warning |
| e2e/cli/test_did_you_mean | Add test for "Did you mean?" suggestions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Build a map from tool name to full IDs for O(1) lookup | ||
| let mut name_to_ids: HashMap<&str, Vec<&str>> = HashMap::new(); | ||
| for id in &ids { | ||
| if let Some((_, name)) = id.rsplit_once('/') { |
There was a problem hiding this comment.
The underscore binding _ discards the owner portion of the ID. Consider using a more descriptive name like _owner to clarify what's being ignored, improving code readability.
| if let Some((_, name)) = id.rsplit_once('/') { | |
| if let Some((_owner, name)) = id.rsplit_once('/') { |
src/aqua/aqua_registry_wrapper.rs
Outdated
| // Map back to full IDs | ||
| let mut results = Vec::new(); | ||
| for matched_name in &similar_names { | ||
| if let Some(full_ids) = name_to_ids.get(matched_name.as_str()) { |
There was a problem hiding this comment.
The .as_str() call is unnecessary here since matched_name is already &str from the iteration. This adds a redundant string conversion.
| if let Some(full_ids) = name_to_ids.get(matched_name.as_str()) { | |
| if let Some(full_ids) = name_to_ids.get(matched_name) { |
There was a problem hiding this comment.
Code Review
This pull request introduces several valuable user experience enhancements, including 'did you mean' suggestions for tool names and warnings for installing inactive tools. The primary fix to skip remote version fetching in prefer-offline mode is also correctly implemented. The code is generally well-structured and includes new tests for the added features. I've identified one critical issue that could lead to a compile error due to a moved value, which should be addressed. Additionally, I've provided a suggestion to improve the performance of the new suggestion feature by caching some data, and a minor wording improvement for a new warning message to enhance clarity.
I am having trouble creating individual review comments. Click here to see my feedback.
src/toolset/tool_version.rs (251-253)
The v variable, which is a String, is moved in other branches of this function (e.g., at line 245 if an installed version is found). This new branch also moves v by calling build(v). If a previous branch that moves v is not taken, this code will compile, but it creates a situation where v is used after being moved if other branches are considered.
This will likely lead to a compile error about using a moved value. To fix this and ensure v can be used in subsequent parts of the function, you should clone it.
I'd also recommend checking other calls to build(v) in this function to ensure they also use v.clone() if v is needed later.
if env::PREFER_OFFLINE.load(std::sync::atomic::Ordering::Relaxed) {
return build(v.clone());
}
src/aqua/aqua_registry_wrapper.rs (121-150)
The name_to_ids map and names vector are rebuilt on every call to aqua_suggest. Since the underlying data from aqua_registry::package_ids() is static, this computation can be cached using LazyLock to improve performance, especially if this function is called multiple times (e.g., with multiple typos).
I suggest creating a static cached structure to hold both the map and the list of names.
use std::sync::LazyLock;
struct AquaSuggestionsCache {
name_to_ids: HashMap<&'static str, Vec<&'static str>>,
names: Vec<&'static str>,
}
static AQUA_SUGGESTIONS_CACHE: LazyLock<AquaSuggestionsCache> = LazyLock::new(|| {
let ids = aqua_registry::package_ids();
let mut name_to_ids: HashMap<&'static str, Vec<&'static str>> = HashMap::new();
for id in &ids {
if let Some((_, name)) = id.rsplit_once('/') {
name_to_ids.entry(name).or_default().push(id);
}
}
let names = name_to_ids.keys().copied().collect();
AquaSuggestionsCache { name_to_ids, names }
});
/// Search aqua packages by tool name, returning "owner/name" IDs
/// where the name part is similar to the query.
pub fn aqua_suggest(query: &str) -> Vec<String> {
// Use a higher threshold (0.8) to avoid noisy suggestions
let similar_names = xx::suggest::similar_n_with_threshold(
query,
&AQUA_SUGGESTIONS_CACHE.names,
5,
0.8,
);
// Map back to full IDs
let mut results = Vec::new();
for matched_name in &similar_names {
if let Some(full_ids) = AQUA_SUGGESTIONS_CACHE.name_to_ids.get(matched_name.as_str()) {
for full_id in full_ids {
results.push(full_id.to_string());
if results.len() >= 5 {
return results;
}
}
}
}
results
}src/cli/install.rs (168)
The warning message specifically mentions mise.toml, but mise can use other configuration files like .tool-versions. A more generic message like "not in any config file" would be more accurate and less confusing for users who might be using other configuration files.
"{tool_list} installed but not activated — {} not in any config file.\nTo install and activate, run:\n{}",
- Only skip remote version fetch for fully-qualified versions (2+ dots) to avoid breaking prefix resolution (e.g. "2" → "2.1.0") - Cache aqua suggestions data with LazyLock instead of rebuilding on every call - Use generic "config file" wording instead of "mise.toml" in install warning since .tool-versions is also valid Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.1 x -- echo |
20.1 ± 0.3 | 19.6 | 23.1 | 1.00 |
mise x -- echo |
20.3 ± 0.2 | 19.8 | 21.6 | 1.01 ± 0.02 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.1 env |
19.6 ± 0.5 | 19.0 | 23.8 | 1.00 |
mise env |
19.7 ± 0.2 | 19.2 | 21.0 | 1.01 ± 0.03 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.1 hook-env |
20.4 ± 0.4 | 19.7 | 24.8 | 1.00 |
mise hook-env |
20.5 ± 0.2 | 20.0 | 21.5 | 1.01 ± 0.02 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.1 ls |
18.5 ± 0.3 | 17.9 | 19.7 | 1.00 |
mise ls |
18.5 ± 0.2 | 18.0 | 19.6 | 1.00 ± 0.02 |
xtasks/test/perf
| Command | mise-2026.2.1 | mise | Variance |
|---|---|---|---|
| install (cached) | 113ms | 110ms | +2% |
| ls (cached) | 70ms | 70ms | +0% |
| bin-paths (cached) | 74ms | 74ms | +0% |
| task-ls (cached) | 544ms | 542ms | +0% |
### 🚀 Features - **(asset-matcher)** enable `mingw-w64` detection for windows packages by @lchagnoleau in [#7981](#7981) - **(crates/vfox)** add download_path to BackendInstall context by @malept in [#7959](#7959) - **(python)** rework `python.uv_venv_auto` setting by @halms in [#7905](#7905) - add "Did you mean?" suggestions and inactive tool warnings by @jdx in [#7965](#7965) ### 🐛 Bug Fixes - **(hook-env)** skip remote version fetching for uninstalled tools in prefer-offline mode by @jdx in [#7976](#7976) - **(install.sh)** Corret `setup` to `set up` by @gogolok in [#7980](#7980) - retry spawn on ETXTBSY (Text file busy) by @jdx in [#7964](#7964) - improve ToolOptions parsing to support comma separated values by @roele in [#7971](#7971) ### 📚 Documentation - improve plugin documentation with comparisons and template links by @jdx in [#7962](#7962) ### 📦️ Dependency Updates - bump hyper-util, system-configuration, lru, aws-sdk, and others by @jdx in [#7977](#7977) ### Chore - **(vfox)** add LuaCATS type definitions for plugin IDE support by @jdx in [#7961](#7961) - **(vfox)** add `download_path` to `BackendInstallCtx` type defintion by @malept in [#7973](#7973) - add stylua linting for vfox plugin Lua files by @jdx in [#7960](#7960) - use system Rust for PPA builds on Ubuntu 26.04+ by @jdx in [#7956](#7956) ### New Contributors - @gogolok made their first contribution in [#7980](#7980) ## 📦 Aqua Registry Updates #### New Packages (4) - [`autobrr/mkbrr`](https://github.com/autobrr/mkbrr) - [`dodobrands/Peekie`](https://github.com/dodobrands/Peekie) - [`grpc/grpc-java/protoc-gen-grpc-java`](https://github.com/grpc/grpc-java/protoc-gen-grpc-java) - [`str4d/age-plugin-yubikey`](https://github.com/str4d/age-plugin-yubikey) #### Updated Packages (3) - [`biomejs/biome`](https://github.com/biomejs/biome) - [`rust-cross/cargo-zigbuild`](https://github.com/rust-cross/cargo-zigbuild) - [`siderolabs/talos`](https://github.com/siderolabs/talos)
…prefer-offline mode (jdx#7976) ## Summary - When a tool version (e.g. `poetry 2.3.2`) is in `.tool-versions` but not installed locally, `resolve_version()` makes an HTTP request to validate the version exists remotely - In prefer-offline mode (`hook-env`, `activate`, `exec`, etc.), this adds ~370ms of latency to every shell prompt - Skip remote version fetching for uninstalled tools when `PREFER_OFFLINE` is true — accept the version string as-is since it will be validated when `mise install` is run Fixes jdx#7974 ## Test plan - [x] `mise run build` — compiles successfully - [x] `mise run test:unit` — all 445 tests pass - [x] `mise run lint` — all lints pass - [ ] Manual test: create `.tool-versions` with `poetry 2.3.2`, run `MISE_TRACE=1 mise hook-env` and verify no HTTP request to mise-versions.jdx.dev 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes version resolution behavior in `PREFER_OFFLINE` mode, which could cause uninstalled pinned versions to be accepted without remote validation until install-time. Low implementation complexity but touches core resolution logic used broadly (hook-env/activate/exec). > > **Overview** > Reduces prompt latency in *prefer-offline* workflows by updating `ToolVersion::resolve_version` to **skip remote version fetching** when `PREFER_OFFLINE` is set and the requested version looks fully pinned (e.g. `x.y.z`) but isn’t installed; prefix requests (e.g. `x`) still resolve remotely. > > Also **memoizes aqua package suggestions** via a process-wide `AQUA_SUGGESTIONS_CACHE` to avoid rebuilding the name-to-ID index on each `aqua_suggest` call, and tweaks the `mise install` warning text to say tools are "not in any config file" (vs only `mise.toml`). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 33626be. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### 🚀 Features - **(asset-matcher)** enable `mingw-w64` detection for windows packages by @lchagnoleau in [jdx#7981](jdx#7981) - **(crates/vfox)** add download_path to BackendInstall context by @malept in [jdx#7959](jdx#7959) - **(python)** rework `python.uv_venv_auto` setting by @halms in [jdx#7905](jdx#7905) - add "Did you mean?" suggestions and inactive tool warnings by @jdx in [jdx#7965](jdx#7965) ### 🐛 Bug Fixes - **(hook-env)** skip remote version fetching for uninstalled tools in prefer-offline mode by @jdx in [jdx#7976](jdx#7976) - **(install.sh)** Corret `setup` to `set up` by @gogolok in [jdx#7980](jdx#7980) - retry spawn on ETXTBSY (Text file busy) by @jdx in [jdx#7964](jdx#7964) - improve ToolOptions parsing to support comma separated values by @roele in [jdx#7971](jdx#7971) ### 📚 Documentation - improve plugin documentation with comparisons and template links by @jdx in [jdx#7962](jdx#7962) ### 📦️ Dependency Updates - bump hyper-util, system-configuration, lru, aws-sdk, and others by @jdx in [jdx#7977](jdx#7977) ### Chore - **(vfox)** add LuaCATS type definitions for plugin IDE support by @jdx in [jdx#7961](jdx#7961) - **(vfox)** add `download_path` to `BackendInstallCtx` type defintion by @malept in [jdx#7973](jdx#7973) - add stylua linting for vfox plugin Lua files by @jdx in [jdx#7960](jdx#7960) - use system Rust for PPA builds on Ubuntu 26.04+ by @jdx in [jdx#7956](jdx#7956) ### New Contributors - @gogolok made their first contribution in [jdx#7980](jdx#7980) ## 📦 Aqua Registry Updates #### New Packages (4) - [`autobrr/mkbrr`](https://github.com/autobrr/mkbrr) - [`dodobrands/Peekie`](https://github.com/dodobrands/Peekie) - [`grpc/grpc-java/protoc-gen-grpc-java`](https://github.com/grpc/grpc-java/protoc-gen-grpc-java) - [`str4d/age-plugin-yubikey`](https://github.com/str4d/age-plugin-yubikey) #### Updated Packages (3) - [`biomejs/biome`](https://github.com/biomejs/biome) - [`rust-cross/cargo-zigbuild`](https://github.com/rust-cross/cargo-zigbuild) - [`siderolabs/talos`](https://github.com/siderolabs/talos)
… mode In prefer-offline mode (used by `mise env`, `hook-env`, `activate`, `exec`), "latest" versions were not covered by the prefer_offline guard, causing unnecessary network calls to package registries. This is especially problematic with private npm registries where the call can hang indefinitely. The existing prefer_offline guard (added in #7976) only covered fully-qualified versions (e.g. "2.3.2"). This adds the same guard for "latest" versions: if a version is already installed, it's returned; otherwise "latest" is returned as-is without a network fetch. Fixes #8499 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… mode (#8500) ## Summary - Adds `prefer_offline` guard for `"latest"` versions in `resolve_version()`, matching the existing guard for fully-qualified versions added in #7976 - Prevents `mise env`, `hook-env`, `activate`, and `exec` from making network calls when resolving `"latest"` versions — avoiding hangs with private npm registries ## Context After lockfiles graduated from experimental in v2026.2.0 (#7929), `mise env --json` with `npm:pkg = "latest"` and no lockfile entry falls through to a network fetch that `prefer_offline` mode was intended to prevent. If the private registry holds the TCP connection open (waiting for credentials), mise hangs indefinitely. PR #7976 fixed this for fully-qualified versions (e.g. `"2.3.2"`) but the `"latest"` path was not covered. This one-liner closes that gap. See #8499 for the full root cause analysis. Fixes #8499 ## Test plan - [ ] `mise env --json` with `npm:pkg = "latest"` and no registry auth no longer makes network calls or hangs - [ ] Existing behavior unchanged when `prefer_offline` is not set (e.g. `mise install` still resolves latest from registry) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small conditional change to version resolution logic that only affects `prefer_offline` behavior for `latest`; low risk but could change expectations for users relying on remote latest lookup in that mode. > > **Overview** > Prevents network access when resolving `"latest"` in `ToolVersion::resolve_version` while `prefer_offline` is enabled, aligning `latest` behavior with the existing prefer-offline skip for fully-qualified versions. > > This adds a guard so `hook-env`/`activate`/`exec`/`env` won’t call `latest_version_with_opts` unless explicitly doing a latest-version lookup (`opts.latest_versions`), avoiding hangs against registries that keep connections open. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1ecf269. 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>
Summary
poetry 2.3.2) is in.tool-versionsbut not installed locally,resolve_version()makes an HTTP request to validate the version exists remotelyhook-env,activate,exec, etc.), this adds ~370ms of latency to every shell promptPREFER_OFFLINEis true — accept the version string as-is since it will be validated whenmise installis runFixes #7974
Test plan
mise run build— compiles successfullymise run test:unit— all 445 tests passmise run lint— all lints pass.tool-versionswithpoetry 2.3.2, runMISE_TRACE=1 mise hook-envand verify no HTTP request to mise-versions.jdx.dev🤖 Generated with Claude Code
Note
Medium Risk
Changes version resolution behavior in
PREFER_OFFLINEmode, which could cause uninstalled pinned versions to be accepted without remote validation until install-time. Low implementation complexity but touches core resolution logic used broadly (hook-env/activate/exec).Overview
Reduces prompt latency in prefer-offline workflows by updating
ToolVersion::resolve_versionto skip remote version fetching whenPREFER_OFFLINEis set and the requested version looks fully pinned (e.g.x.y.z) but isn’t installed; prefix requests (e.g.x) still resolve remotely.Also memoizes aqua package suggestions via a process-wide
AQUA_SUGGESTIONS_CACHEto avoid rebuilding the name-to-ID index on eachaqua_suggestcall, and tweaks themise installwarning text to say tools are "not in any config file" (vs onlymise.toml).Written by Cursor Bugbot for commit 33626be. This will update automatically on new commits. Configure here.