feat(backend): add environment variable override for tool backends#6456
feat(backend): add environment variable override for tool backends#6456
Conversation
Allow overriding tool backends via MISE_BACKENDS_* environment variables. For example, MISE_BACKENDS_GRAPHITE='github:withgraphite/homebrew-tap[exe=gt]' will override the default backend for graphite. This enables users to dynamically select backends without modifying registry.toml or configuration files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull Request Overview
Adds support for MISE_BACKENDS_* environment variables to override tool backends dynamically without modifying configuration files. This enables users to specify alternative backends for tools at runtime.
- Implements environment variable parsing in
RegistryTool::backends()to check for overrides - Adds comprehensive test coverage with both unit and end-to-end tests
- Uses memory leak pattern to handle static lifetime requirements for backend strings
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/registry.rs | Implements env var override logic and adds unit test for functionality |
| e2e/backend/test_backend_env_override | Adds end-to-end test validating full installation flow with graphite tool |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| pub fn backends(&self) -> Vec<&'static str> { | ||
| // Check for environment variable override first | ||
| // e.g., MISE_BACKENDS_GRAPHITE='github:withgraphite/homebrew-tap[exe=gt]' | ||
| let env_key = format!( | ||
| "MISE_BACKENDS_{}", | ||
| self.short.to_uppercase().replace('-', "_") | ||
| ); | ||
| if let Ok(env_value) = env::var(&env_key) { | ||
| // Return the environment variable value as a single backend | ||
| // We need to leak the string to get a 'static lifetime since the registry expects it | ||
| let leaked = Box::leak(env_value.into_boxed_str()); | ||
| return vec![leaked]; | ||
| } |
There was a problem hiding this comment.
This intentional memory leak could accumulate over time if the same tool's backend is overridden multiple times during the program's lifetime. Consider using a static cache or once_cell::sync::Lazy to store and reuse leaked strings for the same environment variable values.
| pub fn backends(&self) -> Vec<&'static str> { | |
| // Check for environment variable override first | |
| // e.g., MISE_BACKENDS_GRAPHITE='github:withgraphite/homebrew-tap[exe=gt]' | |
| let env_key = format!( | |
| "MISE_BACKENDS_{}", | |
| self.short.to_uppercase().replace('-', "_") | |
| ); | |
| if let Ok(env_value) = env::var(&env_key) { | |
| // Return the environment variable value as a single backend | |
| // We need to leak the string to get a 'static lifetime since the registry expects it | |
| let leaked = Box::leak(env_value.into_boxed_str()); | |
| return vec![leaked]; | |
| } |
| // SAFETY: This is safe in a test environment | ||
| unsafe { | ||
| env::set_var("MISE_BACKENDS_NODE", "test:backend"); | ||
| } | ||
| let overridden_backends = tool.backends(); | ||
| assert_eq!(overridden_backends.len(), 1); | ||
| assert_eq!(overridden_backends[0], "test:backend"); | ||
|
|
||
| // Clean up | ||
| // SAFETY: This is safe in a test environment | ||
| unsafe { | ||
| env::remove_var("MISE_BACKENDS_NODE"); | ||
| } |
There was a problem hiding this comment.
Using unsafe blocks for env::set_var is unnecessary as this function is not unsafe. The env::set_var and env::remove_var functions are safe to call and don't require unsafe blocks.
| // SAFETY: This is safe in a test environment | |
| unsafe { | |
| env::set_var("MISE_BACKENDS_NODE", "test:backend"); | |
| } | |
| let overridden_backends = tool.backends(); | |
| assert_eq!(overridden_backends.len(), 1); | |
| assert_eq!(overridden_backends[0], "test:backend"); | |
| // Clean up | |
| // SAFETY: This is safe in a test environment | |
| unsafe { | |
| env::remove_var("MISE_BACKENDS_NODE"); | |
| } | |
| // Set environment variable for test | |
| env::set_var("MISE_BACKENDS_NODE", "test:backend"); | |
| let overridden_backends = tool.backends(); | |
| assert_eq!(overridden_backends.len(), 1); | |
| assert_eq!(overridden_backends[0], "test:backend"); | |
| // Clean up | |
| // Clean up environment variable after test | |
| env::remove_var("MISE_BACKENDS_NODE"); |
| // SAFETY: This is safe in a test environment | ||
| unsafe { | ||
| env::set_var("MISE_BACKENDS_NODE", "test:backend"); | ||
| } | ||
| let overridden_backends = tool.backends(); | ||
| assert_eq!(overridden_backends.len(), 1); | ||
| assert_eq!(overridden_backends[0], "test:backend"); | ||
|
|
||
| // Clean up | ||
| // SAFETY: This is safe in a test environment | ||
| unsafe { | ||
| env::remove_var("MISE_BACKENDS_NODE"); | ||
| } |
There was a problem hiding this comment.
Using unsafe blocks for env::remove_var is unnecessary as this function is not unsafe. The env::set_var and env::remove_var functions are safe to call and don't require unsafe blocks.
| // SAFETY: This is safe in a test environment | |
| unsafe { | |
| env::set_var("MISE_BACKENDS_NODE", "test:backend"); | |
| } | |
| let overridden_backends = tool.backends(); | |
| assert_eq!(overridden_backends.len(), 1); | |
| assert_eq!(overridden_backends[0], "test:backend"); | |
| // Clean up | |
| // SAFETY: This is safe in a test environment | |
| unsafe { | |
| env::remove_var("MISE_BACKENDS_NODE"); | |
| } | |
| env::set_var("MISE_BACKENDS_NODE", "test:backend"); | |
| let overridden_backends = tool.backends(); | |
| assert_eq!(overridden_backends.len(), 1); | |
| assert_eq!(overridden_backends[0], "test:backend"); | |
| // Clean up | |
| env::remove_var("MISE_BACKENDS_NODE"); |
- Use LazyLock with HashMap cache instead of Box::leak to avoid memory leaks - Use heck::ToShoutySnakeCase for consistent case conversion - Support MISE_BACKENDS_* for non-registry tools in BackendArg::full() - Add e2e test for non-registry tool backend overrides This allows users to override backends for any tool, even those not in the registry, enabling maximum flexibility for custom tool configurations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Use [bin=just] and [bin=rg] instead of [exe=...] for consistency with mise's backend option syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
355d44e to
f8a827f
Compare
Document the new environment variable override feature that allows users to dynamically override tool backends using MISE_BACKENDS_<TOOL> pattern. Added documentation in: - backend_architecture.md: Detailed explanation with examples - registry.md: Quick reference for users 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Rearranged the order of backend selection criteria in the documentation to clarify the priority of using an explicit backend over environment variable overrides. Added an example for using the vfox backend for PHP, enhancing the guidance for users on dynamic backend selection. Changes made in: - backend_architecture.md: Updated priority list and added new example for PHP backend.
…usage Revised examples in the registry documentation to demonstrate the use of environment variables for backend overrides. Specifically, added an example for using the vfox backend for PHP, enhancing clarity for users on dynamic backend configuration. Changes made in: - registry.md: Updated examples for backend environment variable usage.
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.21 x -- echo |
20.2 ± 0.6 | 18.8 | 23.1 | 1.02 ± 0.04 |
mise x -- echo |
19.8 ± 0.5 | 19.2 | 25.0 | 1.00 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.21 env |
18.6 ± 0.3 | 18.0 | 20.2 | 1.00 |
mise env |
19.1 ± 0.3 | 18.6 | 20.4 | 1.03 ± 0.02 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.21 hook-env |
19.7 ± 0.7 | 18.3 | 27.8 | 1.00 |
mise hook-env |
21.2 ± 0.8 | 19.3 | 26.3 | 1.07 ± 0.06 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.21 ls |
17.8 ± 0.5 | 16.5 | 19.2 | 1.00 |
mise ls |
18.9 ± 0.7 | 17.2 | 20.8 | 1.06 ± 0.05 |
xtasks/test/perf
| Command | mise-2025.9.21 | mise | Variance |
|---|---|---|---|
| install (cached) | 170ms | ✅ 106ms | +60% |
| ls (cached) | 64ms | 64ms | +0% |
| bin-paths (cached) | 70ms | 70ms | +0% |
| task-ls (cached) | 482ms | 482ms | +0% |
✅ Performance improvement: install cached is 60%
### 📦 Registry - re-enable tests by @risu729 in [#6454](#6454) - restore comments and tests by @risu729 in [#6378](#6378) - add github backend for graphite by @jdx in [#6455](#6455) ### 🚀 Features - **(backend)** add environment variable override for tool backends by @jdx in [#6456](#6456) - add a http_retries setting to define number of retry attempts by @roele in [#6444](#6444) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Bump version to 2025.9.22 with updated completions cache keys, registry tweak for CycloneDX assets, doc star count update, and usage-cli tool bump. > > - **Release/versioning** > - Bump `mise` to `2025.9.22` in `Cargo.toml`, `Cargo.lock`, `default.nix`, `packaging/rpm/mise.spec`, and update version in `README.md`. > - Add `CHANGELOG.md` entry for `2025.9.22`. > - **Completions** > - Update cached usage spec keys in `completions/_mise`, `completions/mise.bash`, and `completions/mise.fish` to `2025_9_22`. > - **Registry** > - Adjust CycloneDX `cyclonedx-cli` assets in `crates/.../cyclonedx-cli/registry.yaml` (remove linux musl overrides in version overrides). > - **Tooling/lockfile** > - Bump `cargo:usage-cli` to `2.3.0` in `mise.lock`. > - **Docs** > - Update stars count in `docs/.vitepress/stars.data.ts` to `19.6k`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5b3be09. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: mise-en-dev <release@mise.jdx.dev> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Summary
MISE_BACKENDS_*environment variables to override tool backendsMotivation
The
MISE_BACKENDS_*environment variable pattern was not working but should have been supported. This PR implements that functionality, allowing users to override the backend for any tool via environment variables.Implementation Details
RegistryTool::backends()insrc/registry.rsto check for environment variable overridesBackendArg::full()to support custom toolsLazyLock<Mutex<HashMap>>for caching instead ofBox::leakheck::ToShoutySnakeCasefor consistent environment variable namingTest plan
test_backend_env_overrideto verify env var override functionalitytest_backend_env_overridefor registry tools (graphite example)test_backend_env_override_non_registryfor non-registry toolsExample usage
Breaking changes
None - this is a new feature that doesn't affect existing behavior.
🤖 Generated with Claude Code
Note
Add support for
MISE_BACKENDS_<TOOL>to override backends (registry and non‑registry), with caching, docs, and tests.BackendArg::full()usingMISE_BACKENDS_<TOOL>(SHOUTY_SNAKE_CASE viaToShoutySnakeCase).RegistryTool::backends()with cached lookups (Lazy<Mutex<HashMap>>).docs/dev-tools/backend_architecture.md.docs/registry.mdand backend architecture docs.test_backend_env_overrideinsrc/registry.rs.e2e/backend/test_backend_env_overridecovering non‑registry tool override.Written by Cursor Bugbot for commit 603802d. This will update automatically on new commits. Configure here.