feat(config): default release age and warn on hidden versions#10279
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughUpdated minimum_release_age handling: a 24-hour built-in default applies only for supported backends, legacy setting alias is normalized, version-date helpers/counts added, CLI lists/upgrades warn when newer releases are hidden, and docs/tests updated. ChangesMinimum Release Age Default and Notifications
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR makes
Confidence Score: 5/5Safe to merge; the previously flagged issues with install_before precedence and minimum_release_age_excludes bypass are both correctly addressed in this revision. The core logic changes in resolve_before_date_with_excludes are correct — excluded tools skip both the global setting and the built-in default, and the normalize_hidden_config_aliases helper preserves the right precedence for the deprecated alias. The new warning paths are additive and fail gracefully. Remaining notes are quality/performance observations that do not affect correctness. No files require special attention. Important Files Changed
Reviews (6): Last reviewed commit: "fix(install): preserve release age for l..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e/backend/test_npm_install_before`:
- Around line 18-21: The test currently calls "mise latest npm:prettier" and
only uses assert_not_contains to check it doesn't return "2.8.8", which can
false-pass on empty/failing output; capture the command output into a variable
(e.g., output=$(mise latest npm:prettier)), then assert the output is non-empty
and additionally assert it does not contain "2.8.8" (or assert it matches a
semantic version regex like ^[0-9]+\.[0-9]+\.[0-9]+$) so that the test fails on
empty/errored results while still ensuring the 0s opt-out returns a concrete
latest version.
In `@src/config/settings.rs`:
- Around line 381-384: The deprecated alias handling currently unconditionally
copies self.install_before into self.minimum_release_age, allowing the
deprecated key to override a user-provided canonical value; update the logic so
install_before only sets minimum_release_age when the canonical setting was not
provided by the user (or apply the alias mapping earlier at the SettingsPartial
layer before defaults are applied). Concretely, either move the alias mapping
out of this post-defaults block into the SettingsPartial normalization step, or
change the if let handling around install_before to check whether
minimum_release_age is unset (or its source is not user-provided) before
assigning, using the identifiers install_before, minimum_release_age, and
SettingsPartial to locate the correct code to change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1b629010-1bf7-4b9e-b0ca-57ef6e3989c6
📒 Files selected for processing (10)
docs/dev-tools/backends/npm.mddocs/dev-tools/backends/pipx.mddocs/dev-tools/mise-lock.mddocs/tips-and-tricks.mde2e/backend/test_npm_install_beforee2e/backend/test_npm_package_managerschema/mise.jsonsettings.tomlsrc/config/settings.rssrc/install_before.rs
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.6.1 x -- echo |
21.2 ± 1.5 | 17.7 | 27.9 | 1.00 |
mise x -- echo |
23.3 ± 2.6 | 17.9 | 55.2 | 1.10 ± 0.15 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.6.1 env |
21.1 ± 1.6 | 17.6 | 26.8 | 1.00 |
mise env |
22.9 ± 1.9 | 18.2 | 29.4 | 1.08 ± 0.12 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.6.1 hook-env |
22.0 ± 1.7 | 18.2 | 28.8 | 1.00 |
mise hook-env |
24.0 ± 2.0 | 19.2 | 30.7 | 1.09 ± 0.13 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.6.1 ls |
17.9 ± 1.6 | 14.6 | 22.6 | 1.00 |
mise ls |
19.6 ± 1.6 | 15.2 | 24.2 | 1.09 ± 0.13 |
xtasks/test/perf
| Command | mise-2026.6.1 | mise | Variance |
|---|---|---|---|
| install (cached) | 157ms | 158ms | +0% |
| ls (cached) | 69ms | 70ms | -1% |
| bin-paths (cached) | 78ms | 81ms | -3% |
| task-ls (cached) | 147ms | 148ms | +0% |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/backend/mod.rs (2)
174-183: ⚡ Quick winRemove redundant import.
Line 175 imports
parse_into_timestamplocally, but it's already imported at module scope on line 20.♻️ Suggested cleanup
pub fn hidden_by_date(&self, before: Timestamp) -> bool { - use crate::duration::parse_into_timestamp; match &self.created_at { Some(ts) => match parse_into_timestamp(ts) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/mod.rs` around lines 174 - 183, The local use statement importing parse_into_timestamp inside the hidden_by_date method is redundant because parse_into_timestamp is already imported at module scope; remove the inner "use crate::duration::parse_into_timestamp;" line from the hidden_by_date method (leaving the rest of the function intact) so the method uses the module-scoped parse_into_timestamp when converting created_at to a Timestamp.
191-208: ⚡ Quick winConsider refactoring to avoid double timestamp parsing.
When a version has a valid
created_attimestamp that passes the date filter (created < before), this code parses the timestamp twice:
- Inside
hidden_by_dateat line 177- Again at line 199 to check if parsing fails
The second parse at line 199 will only fail if
hidden_by_datereturned false due to a parse error, meaning both parses will fail and the trace will log. For the common case where the timestamp is valid and passes the filter, we parse successfully twice, which is wasteful.♻️ Potential optimization
One approach: have
filter_by_dateparse once and share the result:pub fn filter_by_date(versions: Vec<Self>, before: Timestamp) -> Vec<Self> { versions .into_iter() .filter(|v| match &v.created_at { - Some(ts) => { - if v.hidden_by_date(before) { - false - } else { - if crate::duration::parse_into_timestamp(ts).is_err() { - trace!("Failed to parse timestamp: {}", ts); - } - true - } - } + Some(ts) => match parse_into_timestamp(ts) { + Ok(created) => { + if created >= before { + false // hidden + } else { + true // include + } + } + Err(_) => { + trace!("Failed to parse timestamp: {}", ts); + true // include by default + } + }, None => true, }) .collect() }This preserves the exact same behavior (including the trace) but parses each timestamp only once.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/mod.rs` around lines 191 - 208, The filter_by_date implementation currently parses each version.created_at twice (once inside hidden_by_date and once again via parse_into_timestamp), so change filter_by_date to parse created_at only once: for each version with Some(ts) call crate::duration::parse_into_timestamp(ts) once, match on that Result and if Ok(parsed_ts) compare parsed_ts to the before Timestamp to decide whether to filter (same logic as hidden_by_date), and if Err(_) emit the same trace and keep the version; alternatively, refactor hidden_by_date to accept a pre-parsed timestamp (e.g., hidden_by_date_parsed) and reuse the parse result from filter_by_date to avoid double parsing while preserving current behavior.src/cli/ls_remote.rs (1)
126-142: ⚡ Quick winConsider consolidating prefix filtering to avoid duplicate work.
The current implementation filters by prefix twice:
- Lines 130-134: Filter, clone, and collect to count hidden versions
- Line 141: Filter by prefix again for the final output
This could be more efficient by filtering once:
♻️ Potential optimization
let all_versions = plugin.list_remote_versions_with_info(config).await?; +let prefix_matched: Vec<_> = all_versions + .into_iter() + .filter(|v| matches_prefix(&v.version)) + .collect(); let hidden_versions = before_date .map(|before| { - VersionInfo::count_hidden_by_date( - &all_versions - .iter() - .filter(|v| matches_prefix(&v.version)) - .cloned() - .collect::<Vec<_>>(), - before, - ) + VersionInfo::count_hidden_by_date(&prefix_matched, before) }) .unwrap_or_default(); -let versions = filter_versions_by_date(all_versions, before_date) - .into_iter() - .filter(|v| matches_prefix(&v.version)) - .collect::<Vec<_>>(); +let versions = filter_versions_by_date(prefix_matched, before_date);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/cli/ls_remote.rs` around lines 126 - 142, The prefix filtering is done twice; fix by filtering all_versions once into a single Vec (e.g., create filtered_versions = all_versions.iter().filter(|v| matches_prefix(&v.version)).cloned().collect::<Vec<_>>()), then pass filtered_versions.clone() (or references) into VersionInfo::count_hidden_by_date for hidden_versions and use filtered_versions as the input to compute versions after filter_versions_by_date; update uses of all_versions in the surrounding block to reference filtered_versions where appropriate (symbols: all_versions, filtered_versions, VersionInfo::count_hidden_by_date, filter_versions_by_date, matches_prefix, versions).src/cli/upgrade.rs (1)
631-671: 💤 Low valueConsider renaming for clarity: this method applies the global/per-tool cutoff, not "unfiltered" latest.
The method name
unfiltered_latest_for_upgradesuggests it retrieves versions without any release-age filtering, but the implementation callslist_versions_matching, which resolves the cutoff from global and per-tool settings (via the defaultminimum_release_age = "24h"or configured values).The current behavior is correct for the intended warning logic: comparing CLI-flag-filtered versions against default/global-filtered versions. However, the name might confuse future maintainers who expect "unfiltered" to mean "no date filtering at all."
💡 Alternative naming
Consider renaming to something like:
default_filtered_latest_for_upgradebaseline_latest_for_upgradeglobally_filtered_latest_for_upgradeOr add a comment explaining that "unfiltered" means "without the CLI override, but still respecting global/per-tool cutoffs."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/cli/upgrade.rs` around lines 631 - 671, Rename or clarify the method currently named unfiltered_latest_for_upgrade to avoid implying it ignores release-age cutoffs; either rename it to a clearer identifier like baseline_latest_for_upgrade or globally_filtered_latest_for_upgrade, and update all callers to the new name (or alternatively add a clear doc comment above unfiltered_latest_for_upgrade stating that it applies global/per-tool cutoff via list_versions_matching and respects minimum_release_age). Make sure references to symbols in the body (e.g., list_versions_matching, list_remote_versions, split_version_prefix, prefixed_latest_query, and ToolVersion::request) remain correct after the rename or that the new doc comment explains that “unfiltered” means “without CLI override but still respecting global/per-tool cutoffs.”
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e/cli/test_install_before`:
- Around line 42-47: The test asserts a "newer jq release" warning but never
ensures jq is already installed at an older version, making the check flaky;
before running the upgrade dry-run (the call to mise upgrade jq
--minimum-release-age ... --dry-run) ensure the environment has a known older jq
installed (for example by invoking mise install jq@2018-01-01 or the equivalent
mise install jq --version <old-date>) so the upgrade path will deterministically
emit the "newer jq release" warning, then run the existing output capture and
assert_contains on the upgrade output.
---
Nitpick comments:
In `@src/backend/mod.rs`:
- Around line 174-183: The local use statement importing parse_into_timestamp
inside the hidden_by_date method is redundant because parse_into_timestamp is
already imported at module scope; remove the inner "use
crate::duration::parse_into_timestamp;" line from the hidden_by_date method
(leaving the rest of the function intact) so the method uses the module-scoped
parse_into_timestamp when converting created_at to a Timestamp.
- Around line 191-208: The filter_by_date implementation currently parses each
version.created_at twice (once inside hidden_by_date and once again via
parse_into_timestamp), so change filter_by_date to parse created_at only once:
for each version with Some(ts) call crate::duration::parse_into_timestamp(ts)
once, match on that Result and if Ok(parsed_ts) compare parsed_ts to the before
Timestamp to decide whether to filter (same logic as hidden_by_date), and if
Err(_) emit the same trace and keep the version; alternatively, refactor
hidden_by_date to accept a pre-parsed timestamp (e.g., hidden_by_date_parsed)
and reuse the parse result from filter_by_date to avoid double parsing while
preserving current behavior.
In `@src/cli/ls_remote.rs`:
- Around line 126-142: The prefix filtering is done twice; fix by filtering
all_versions once into a single Vec (e.g., create filtered_versions =
all_versions.iter().filter(|v|
matches_prefix(&v.version)).cloned().collect::<Vec<_>>()), then pass
filtered_versions.clone() (or references) into VersionInfo::count_hidden_by_date
for hidden_versions and use filtered_versions as the input to compute versions
after filter_versions_by_date; update uses of all_versions in the surrounding
block to reference filtered_versions where appropriate (symbols: all_versions,
filtered_versions, VersionInfo::count_hidden_by_date, filter_versions_by_date,
matches_prefix, versions).
In `@src/cli/upgrade.rs`:
- Around line 631-671: Rename or clarify the method currently named
unfiltered_latest_for_upgrade to avoid implying it ignores release-age cutoffs;
either rename it to a clearer identifier like baseline_latest_for_upgrade or
globally_filtered_latest_for_upgrade, and update all callers to the new name (or
alternatively add a clear doc comment above unfiltered_latest_for_upgrade
stating that it applies global/per-tool cutoff via list_versions_matching and
respects minimum_release_age). Make sure references to symbols in the body
(e.g., list_versions_matching, list_remote_versions, split_version_prefix,
prefixed_latest_query, and ToolVersion::request) remain correct after the rename
or that the new doc comment explains that “unfiltered” means “without CLI
override but still respecting global/per-tool cutoffs.”
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a0d3376d-027d-4b64-b771-6b1b7f4e42fc
📒 Files selected for processing (6)
e2e/cli/test_install_beforee2e/cli/test_ls_remotesrc/backend/mod.rssrc/cli/ls_remote.rssrc/cli/upgrade.rssrc/toolset/outdated_info.rs
✅ Files skipped from review due to trivial changes (1)
- src/toolset/outdated_info.rs
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/config/settings.rs (1)
289-291:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftPreserve env-over-file precedence for the deprecated alias.
These builder calls only normalize
install_beforeon the CLI partials. If a user exportsMISE_INSTALL_BEFORE=7dand a config file setsminimum_release_age="3d",sb.load()will keep both fields, andset_hidden_configs()later preserves the lower-precedence canonical value becauseminimum_release_ageis already populated. Please normalize the env alias before layering too, and add a regression for that env/file combination.Also applies to: 305-307
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/config/settings.rs` around lines 289 - 291, The CLI env alias needs normalization before layering so env-over-file precedence is preserved: call normalize_hidden_config_aliases on the CLI partial (the value from CLI_SETTINGS.lock().unwrap().clone().unwrap_or_default()) before passing it into the builder’s preloaded() / preloaded_env() layering logic (i.e., ensure normalize_hidden_config_aliases runs prior to sb.load()/preloaded), so that deprecated env keys like MISE_INSTALL_BEFORE map to the canonical field (install_before/minimum_release_age) before file values are applied; update the code paths that currently normalize only install_before (the preloaded(...) calls around normalize_hidden_config_aliases at lines shown and similar calls at 305-307) and add a regression test that sets the env var (MISE_INSTALL_BEFORE=7d) and a config file with minimum_release_age="3d" and asserts the env value wins after set_hidden_configs().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/config/settings.rs`:
- Around line 289-291: The CLI env alias needs normalization before layering so
env-over-file precedence is preserved: call normalize_hidden_config_aliases on
the CLI partial (the value from
CLI_SETTINGS.lock().unwrap().clone().unwrap_or_default()) before passing it into
the builder’s preloaded() / preloaded_env() layering logic (i.e., ensure
normalize_hidden_config_aliases runs prior to sb.load()/preloaded), so that
deprecated env keys like MISE_INSTALL_BEFORE map to the canonical field
(install_before/minimum_release_age) before file values are applied; update the
code paths that currently normalize only install_before (the preloaded(...)
calls around normalize_hidden_config_aliases at lines shown and similar calls at
305-307) and add a regression test that sets the env var
(MISE_INSTALL_BEFORE=7d) and a config file with minimum_release_age="3d" and
asserts the env value wins after set_hidden_configs().
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d69e956f-010a-4644-ac8d-8246c05c694e
📒 Files selected for processing (3)
src/backend/mod.rssrc/cli/ls_remote.rssrc/config/settings.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/cli/ls_remote.rs
50d80ec to
e394605
Compare
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit 3f09da5. Configure here.
…version resolution The built-in 24h minimum_release_age default (#10279) set a before_date on every resolution, which disabled the installed-version fast paths for all non-prefer-offline commands (mise which, mise use, ...). Every invocation then fetched remote version lists for the entire toolset, turning ~75ms shell-startup calls into ~2.5s each (reported as ~65s shell startups in discussion #10308). The built-in default now only gates which versions remote resolution may pick — its actual purpose of delaying new installs. Explicit cutoffs (the --minimum-release-age flag, a per-tool minimum_release_age option, or the minimum_release_age setting) keep their date-aware resolution semantics from #9269, since opting in was a deliberate choice there. - install_before: cutoff resolution reports a BeforeDateSource (Provided/Explicit/Default) alongside the timestamp - ResolveOptions: new before_date_from_default flag, set via a shared apply_before_date_for_tool helper; the should_filter_installed_versions gates in resolve_version/resolve_prefix require an explicit cutoff - e2e: test_which_no_remote_fetch pins the regression (fails on unfixed code); test_hook_env_no_remote_fetch guards the prefer-offline paths so hook-env can never start fetching remote versions Fixes #10308 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Summary
minimum_release_ageto24hat runtime for backends that can use release timestampsminimum_release_ageoptional internally so explicit values keep precedence over deprecatedinstall_beforeTests
mise run render:schemacargo fmt --all -- --checkcargo test install_beforecargo test latest_version_testscargo test test_settings_toml_is_sortedmise run test:e2e e2e/cli/test_lock_env e2e/cli/test_lock_versionMISE_GPG_VERIFY=false mise run test:e2e e2e/cli/test_install_beforeMISE_GPG_VERIFY=false mise run test:e2e e2e/backend/test_npm_install_beforemise run lintThis PR description was generated by an AI coding assistant.
Note
High Risk
Changes default version resolution for many backends without explicit config, which can surprise upgrades and CI; behavior is mitigated by
0s, exclusions, and pinned versions bypassing the filter.Overview
Supply-chain default: Fuzzy version resolution on timestamp-capable backends (core, aqua, github, npm, pipx, etc.) now applies a built-in 24h
minimum_release_agewhen no global or per-tool value is set.minimum_release_age = "0s"turns that off.minimum_release_age_excludesskips both the global setting and this default. Plugin-style backends without release metadata (e.g. asdf) are unchanged.Visibility:
mise ls-remotewarns how many releases were hidden;mise upgradecompares date-filtered vs unfiltered latest and warns when a newer release is ignored (e.g.newer jq release … ignored by minimum_release_age).Resolution plumbing:
install_beforeis normalized intominimum_release_age(explicitminimum_release_agewins).Backend::latest_version_unfilteredsupports upgrade diagnostics. Pinned exact versions no longer forward release-age cutoffs into npm/pipx transitive installs.VersionInfogains shared date-filter helpers.Docs/schema: Settings and security docs document the 24h default, exclusions, and opt-out; e2e tests cover
0s, upgrade warnings, and ls-remote warnings.Reviewed by Cursor Bugbot for commit 49a10b9. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
Documentation
Tests
Refactor