Skip to content

feat(env): add environment caching with module cacheability support#7761

Merged
jdx merged 11 commits into
mainfrom
feat/env-cache-fnox
Jan 19, 2026
Merged

feat(env): add environment caching with module cacheability support#7761
jdx merged 11 commits into
mainfrom
feat/env-cache-fnox

Conversation

@jdx

@jdx jdx commented Jan 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds environment caching for mise with encrypted session-scoped storage
  • Extends vfox MiseEnvResult with cacheable and watch_files fields for module cacheability
  • Adds --fresh-env/-F flag to exec and run commands to bypass cache

Implementation Details

Cache System

  • Cache stored at ~/.local/state/mise/env-cache/
  • ChaCha20-Poly1305 encryption with session-scoped key
  • Key generated on mise activate and exported as __MISE_ENV_CACHE_KEY
  • Key is lost when shell session ends (provides security)

Cache Invalidation

Cache invalidates when:

  • Config file mtimes change
  • Tool versions change
  • Settings change
  • mise version changes
  • TTL expires (default 1h, configurable via env_cache_ttl)
  • Module watch_files change

New Settings

env_cache = true  # Enable caching (default: false)
env_cache_ttl = "1h"  # Cache TTL (default: 1h)

Module Cacheability

vfox plugins can now return cache metadata:

return {
    cacheable = true,
    watch_files = {"/path/to/config"},
    env = {{key = "FOO", value = "bar"}}
}

Legacy format (array of env vars) still works and is treated as uncacheable for backward compatibility.

Test plan

  • mise run test:e2e test_env_cache - Basic caching tests
  • mise run test:e2e test_env_cache_fresh - Tests for --fresh-env flag
  • Verify cache directory is created when caching enabled
  • Verify cache invalidates on config change
  • Manual testing with MISE_ENV_CACHE=1 in shell

🤖 Generated with Claude Code


Note

Introduces an encrypted, session-scoped environment cache and integrates it across env resolution, CLI, and settings.

  • Implements ChaCha20-Poly1305–encrypted env cache at ~/.local/state/mise/env-cache with TTL (env_cache_ttl), key via __MISE_ENV_CACHE_KEY generated on activate; cache load/save, watch-file mtime validation, and invalidation on config/tool/settings/version changes
  • Extends vfox MiseEnv to return {env, cacheable, watch_files}; consumers updated to track cacheability and watch files
  • Integrates caching into toolset env computation and PATH; adds --fresh-env to exec/run (and tasks/shims propagation) to bypass cache
  • Adds settings env_cache and env_cache_ttl (schema + settings.toml), and includes env cache in cache prune/cache clear
  • Updates docs/man/usage and adds e2e tests for env cache and --fresh-env
  • Adds dependencies hex and chacha20poly1305

Written by Cursor Bugbot for commit 77b1d29. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings January 19, 2026 18:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 environment caching to mise to improve performance for nested invocations, using session-scoped encrypted storage and comprehensive cache invalidation mechanisms.

Changes:

  • Implements encrypted environment caching system with ChaCha20-Poly1305 encryption and session-scoped keys
  • Extends vfox plugin system to support module cacheability metadata via MiseEnvResult structure
  • Adds --fresh-env/-F flags to exec and run commands to bypass cache on demand

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/toolset/env_cache.rs New module implementing core cache system with encryption, TTL, and watch file validation
src/toolset/toolset_env.rs Integrates cache loading/saving logic into environment computation
src/plugins/vfox_plugin.rs Updates mise_env to return MiseEnvResponse with cache metadata
crates/vfox/src/hooks/mise_env.rs Implements MiseEnvResult with backward-compatible legacy format support
src/cli/exec.rs Adds --fresh-env flag and cache key propagation to exec command
src/cli/run.rs Adds --fresh-env flag to run command
src/cli/activate.rs Generates and exports encryption key during shell activation
src/config/env_directive/module.rs Tracks module cacheability and watch files in environment results
settings.toml Defines env_cache and env_cache_ttl configuration settings
e2e/env/test_env_cache Adds end-to-end tests for basic caching functionality
e2e/env/test_env_cache_fresh Adds tests for --fresh-env flag behavior

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/toolset/toolset_env.rs Outdated
Comment on lines +137 to +141
let base_path = env::PATH
.iter()
.map(|p| p.to_string_lossy())
.collect::<Vec<_>>()
.join(":");

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path separator is hardcoded as : which is Unix-specific. This will not work correctly on Windows where the separator is ;. Use std::path::MAIN_SEPARATOR or a cross-platform approach to join paths.

Copilot uses AI. Check for mistakes.
Comment thread src/toolset/env_cache.rs Outdated
Comment on lines +88 to +89
let key: [u8; 32] = rand::random();
BASE64_STANDARD.encode(key)

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using rand::random() for cryptographic key generation is not cryptographically secure. Use ChaCha20Poly1305::generate_key(&mut OsRng) or a CSPRNG like rand::rngs::OsRng to ensure secure random key generation.

Suggested change
let key: [u8; 32] = rand::random();
BASE64_STANDARD.encode(key)
let key = ChaCha20Poly1305::generate_key(&mut OsRng);
BASE64_STANDARD.encode(&key)

Copilot uses AI. Check for mistakes.
Comment thread src/cli/run.rs Outdated
Comment on lines +222 to +226
// SAFETY: We're only unsetting a mise-specific env var, not affecting other
// processes or causing data races
unsafe {
std::env::remove_var("__MISE_ENV_CACHE_KEY");
}

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using unsafe to modify environment variables is unnecessary here. While the comment explains the reasoning, std::env::remove_var is safe in Rust and doesn't require an unsafe block. Remove the unsafe wrapper.

Suggested change
// SAFETY: We're only unsetting a mise-specific env var, not affecting other
// processes or causing data races
unsafe {
std::env::remove_var("__MISE_ENV_CACHE_KEY");
}
// We're only unsetting a mise-specific env var, not affecting other
// processes or causing data races
std::env::remove_var("__MISE_ENV_CACHE_KEY");

Copilot uses AI. Check for mistakes.
Comment thread src/cli/exec.rs Outdated
Comment on lines +69 to +73
// SAFETY: We're only unsetting a mise-specific env var, not affecting other
// processes or causing data races
unsafe {
std::env::remove_var("__MISE_ENV_CACHE_KEY");
}

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using unsafe to modify environment variables is unnecessary here. While the comment explains the reasoning, std::env::remove_var is safe in Rust and doesn't require an unsafe block. Remove the unsafe wrapper.

Suggested change
// SAFETY: We're only unsetting a mise-specific env var, not affecting other
// processes or causing data races
unsafe {
std::env::remove_var("__MISE_ENV_CACHE_KEY");
}
std::env::remove_var("__MISE_ENV_CACHE_KEY");

Copilot uses AI. Check for mistakes.
Comment thread crates/vfox/src/hooks/mise_env.rs Outdated
} else {
// Legacy format: table is actually an array of env keys
// Try to parse as array
let env: Vec<EnvKey> = Vec::from_lua(Value::Table(table), lua)?;

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If parsing as an array fails, the error message won't clearly indicate this is a legacy format issue. Consider wrapping this with a more descriptive error message using map_err to help users understand the format requirements.

Suggested change
let env: Vec<EnvKey> = Vec::from_lua(Value::Table(table), lua)?;
let env: Vec<EnvKey> = Vec::from_lua(Value::Table(table), lua).map_err(|e| {
LuaError::RuntimeError(format!(
"Failed to parse legacy MiseEnv hook result as an array of env keys. \
Expected an array like {{ {{\"KEY\", \"VALUE\"}}, ... }}. Underlying error: {e}"
))
})?;

Copilot uses AI. Check for mistakes.
Comment thread Cargo.toml
sha1 = "0.10"
sha2 = "0.10"
blake3 = "1"
chacha20poly1305 = "0.10"

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version 0.10 of chacha20poly1305 may not be the latest. Consider using a more recent version to benefit from security updates and improvements. Check crates.io for the latest stable version.

Suggested change
chacha20poly1305 = "0.10"
chacha20poly1305 = "0.10.1"

Copilot uses AI. Check for mistakes.
@github-actions

github-actions Bot commented Jan 19, 2026

Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.1.5 x -- echo 15.1 ± 0.4 14.1 16.4 1.00
mise x -- echo 15.3 ± 0.4 14.3 17.3 1.01 ± 0.04

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.1.5 env 15.9 ± 0.6 14.0 17.7 1.00
mise env 16.6 ± 0.6 15.5 22.1 1.05 ± 0.05

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.1.5 hook-env 16.1 ± 0.4 15.1 17.3 1.00 ± 0.04
mise hook-env 16.1 ± 0.5 15.0 18.8 1.00

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.1.5 ls 14.2 ± 0.4 13.2 15.8 1.00
mise ls 14.2 ± 0.4 13.2 15.3 1.00 ± 0.04

xtasks/test/perf

Command mise-2026.1.5 mise Variance
install (cached) 82ms 82ms +0%
ls (cached) 54ms 54ms +0%
bin-paths (cached) 55ms 55ms +0%
task-ls (cached) 232ms 233ms +0%

jdx and others added 5 commits January 19, 2026 16:16
Implements environment caching for mise with encrypted session-scoped storage:

- Add env_cache and env_cache_ttl settings to enable/configure caching
- Create src/toolset/env_cache.rs with ChaCha20-Poly1305 encryption
- Cache keys based on config files, tool versions, settings, and base PATH
- Extend vfox MiseEnvResult with cacheable and watch_files fields
- Update module.rs to handle cache metadata from vfox plugins
- Add --fresh-env/-F flag to exec and run commands to bypass cache
- Generate session-scoped encryption key on activate
- Propagate cache key to subprocesses for nested invocations
- Add e2e tests for env caching

The cache is encrypted with a session-scoped key that is:
- Generated on `mise activate` and exported as __MISE_ENV_CACHE_KEY
- Lost when the shell session ends (provides security)
- Propagated to child processes for nested mise invocations

Cache invalidates when:
- Config file mtimes change
- Tool versions change
- Settings change
- mise version changes
- TTL expires (default 1h)
- Module watch_files change

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove -F short alias from --fresh-env flag (fixes sorted subcommands test)
- Add reset_env_cache_key() helper in env.rs for safe env var removal
- Use std::env::join_paths for platform-appropriate PATH separator
- Add #[allow(dead_code)] to clear/clear_stale functions (not yet used)
- Update e2e test comments to remove -F references

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The clap-sort library requires long-only flags to be in alphabetical
order. Move --fresh-env before --no-prepare in both commands.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jdx jdx force-pushed the feat/env-cache-fnox branch from d77c1b1 to 4102d69 Compare January 19, 2026 22:20
autofix-ci Bot and others added 2 commits January 19, 2026 22:24
- Remove unused clear_stale() function from env_cache.rs
- Add env cache clearing to `mise cache clear`
- Add env cache pruning to `mise cache prune` (uses env_cache_ttl)
- Add env cache pruning to auto_prune() (uses env_cache_ttl)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
cursor[bot]

This comment was marked as outdated.

jdx and others added 2 commits January 19, 2026 16:46
- Use CSPRNG (ChaCha20Poly1305::generate_key) for key generation
- Use saturating_sub in TTL validation to prevent integer underflow
- Improve error message for MiseEnv hook parsing failures

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add documentation for the extended MiseEnv return format with
cacheable and watch_files options for env plugin development.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
cursor[bot]

This comment was marked as outdated.

- Add plugin directory to watch_files for cache invalidation
  This ensures env cache invalidates when plugin is updated via
  `mise plugins update` or manual edit
- Fix silent type error swallowing in MiseEnvResult parsing
  Now properly reports type errors for env, cacheable, and watch_files
  fields instead of silently using defaults

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
cursor[bot]

This comment was marked as outdated.

- Absolutize relative watch_files paths in module.rs
  Ensures consistent cache validation regardless of which directory
  mise is run from
- Use ensure_encryption_key() in task executor for consistent behavior
  Now both `mise exec` and `mise run` enable caching for subprocesses

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.


// Add plugin directory to watch files for cache invalidation
// This ensures cache invalidates when plugin is updated
r.watch_files.push(path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin directory mtime doesn't detect file modifications

Medium Severity

The plugin directory path is added to watch_files for cache invalidation, but directory mtimes only change when files are added or removed, not when existing files are modified. This means updating a plugin's source code (like editing hooks/mise_env.lua) won't invalidate the cache, causing stale cached environment values to persist. The comment claims this "ensures cache invalidates when plugin is updated" but that's incorrect for file modifications.

Fix in Cursor Fix in Web

@jdx jdx merged commit 8bcae3b into main Jan 19, 2026
36 checks passed
@jdx jdx deleted the feat/env-cache-fnox branch January 19, 2026 23:34
jdx pushed a commit that referenced this pull request Jan 20, 2026
### 🚀 Features

- **(config)** add miette diagnostics for TOML parsing errors by @jdx in
[#7764](#7764)
- **(env)** add environment caching with module cacheability support by
@jdx in [#7761](#7761)

### 🐛 Bug Fixes

- **(prepare)** handle freshness check for auto-created venvs by @jdx in
[#7770](#7770)
- **(release)** use colon separator in release titles by @jdx in
[#7765](#7765)
- **(release)** drop Fedora 41 from COPR build (EOL) by @TobiX in
[#7771](#7771)

### 🚜 Refactor

- improve filetask field parsing tests and parser by @makp0 in
[#7751](#7751)

### 📚 Documentation

- improve CLAUDE.md with additional development guidance by @jdx in
[#7763](#7763)
- drop architecture from Debian sources.list by @TobiX in
[#7772](#7772)

### 📦 Registry

- use aqua for zprint by @scop in
[#7767](#7767)

### Security

- remove insecure registry-comment workflow by @jdx in
[#7769](#7769)

## 📦 Aqua Registry Updates

#### New Packages (2)

-
[`cameron-martin/bazel-lsp`](https://github.com/cameron-martin/bazel-lsp)
- [`micro-editor/micro`](https://github.com/micro-editor/micro)
german-molins pushed a commit to german-molins/dotfiles that referenced this pull request Jan 22, 2026
…caching

Mise v2025.5.6 includes built-in environment caching for 'mise activate'
that provides module-level cacheability for faster shell startup times.
See: jdx/mise#7761

Removed:
- _mise_evalcache() and _mise_evalcache_clear() functions from 17-mise.sh
- bash:clear-mise-cache task reference from mise:update task
- Custom mise caching documentation from bash.md

The previous custom solution cached 'mise activate bash' output to
~/.cache/dotfiles/bash/mise/activate_bash.sh with manual invalidation
based on mise binary and installs directory timestamps.

The first-party solution is more robust as it handles environment
resolution caching at the module level, particularly benefiting projects
with many tools or complex configurations.

Note: General purpose _evalcache for other tools (zoxide, atuin, etc.)
remains unchanged in 05-cache.sh.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants