Skip to content

feat: add --before flag for date-based version filtering#7298

Merged
jdx merged 13 commits intomainfrom
install-before-feature
Dec 15, 2025
Merged

feat: add --before flag for date-based version filtering#7298
jdx merged 13 commits intomainfrom
install-before-feature

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Dec 14, 2025

Summary

Add the ability to filter tool versions by release date, similar to npm's --before flag. This enables reproducible builds and supply-chain security by ensuring only versions released before a certain date are considered.

Features:

  • New install_before setting (env: MISE_INSTALL_BEFORE)
  • New --before CLI flag for install, upgrade, and use commands
  • Supports absolute dates (2024-06-01) and relative durations (90d, 1y)
  • Only filters fuzzy version requests (node@20, latest) - explicit versions like node@22.5.0 are not filtered

Usage examples:

# Install node version released before June 2024
mise install node@20 --before 2024-06-01

# Use relative duration (90 days ago)
mise upgrade --before 90d

# Set globally via environment variable
export MISE_INSTALL_BEFORE="2024-06-01"
mise install node@latest

Depends on: #7294 (for versions_host timestamps)

Test plan

  • Unit tests pass
  • E2E test added (test_install_before)
  • Manual testing with various date formats
  • Test with backends that provide timestamps

🤖 Generated with Claude Code


Note

Adds date-aware version resolution/filtering across install/upgrade/use via --before and MISE_INSTALL_BEFORE, with docs, schema, man, completions, and tests updated.

  • Core resolution:
    • Introduce ResolveOptions.before_date and date parsing (parse_into_timestamp).
    • Add backend helpers: list_versions_matching_with_opts and latest_version_with_opts using VersionInfo.created_at.
    • Apply date filter in ToolVersion::resolve, latest_version_with_opts, and outdated checks.
  • CLI:
    • Add --before <BEFORE> to install, upgrade, and use; wire through install/upgrade flows.
    • Respect explicit pinned versions when filtering.
  • Settings/Schema:
    • New settings.install_before (env: MISE_INSTALL_BEFORE) in settings.toml and schema/mise.json.
  • Docs/UX:
    • Update docs/cli/*, man/man1/mise.1, mise.usage.kdl, and Fig completions to document --before.
  • Backends:
    • Use VersionInfo::filter_by_date; core Rust plugin and outdated logic accept ResolveOptions.
  • Tests:
    • Add e2e e2e/cli/test_install_before for flag and env behavior.

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

jdx and others added 3 commits December 14, 2025 15:40
…ugins

Add created_at timestamp support to the following core plugins:
- Node.js: extracts date from nodejs.org/dist/index.json
- Zig: extracts date from ziglang.org/download/index.json
- Rust: extracts created_at from GitHub releases
- Bun: extracts created_at from GitHub releases
- Swift: extracts created_at from GitHub releases
- Erlang: extracts created_at from GitHub releases (when compile=false)
- Elixir: extracts timestamp from builds.hex.pm/builds/elixir/builds.txt

Each plugin now implements _list_remote_versions_with_info() to provide
version metadata. This allows `mise ls-remote --json` to include
created_at timestamps for these tools.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The versions host has a .toml endpoint that includes created_at
timestamps. Use this instead of plain text to ensure timestamps
are available when the versions host succeeds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add the ability to filter tool versions by release date, similar to npm's
--before flag. This enables reproducible builds and supply-chain security
by ensuring only versions released before a certain date are considered.

Features:
- New `install_before` setting (env: MISE_INSTALL_BEFORE)
- New `--before` CLI flag for install, upgrade, and use commands
- Supports absolute dates (2024-06-01) and relative durations (90d, 1y)
- Only filters fuzzy version requests (node@20, latest) - explicit versions
  like node@22.5.0 are not filtered

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings December 14, 2025 22:13
Comment thread src/backend/mod.rs
trace!("Failed to parse timestamp: {}", ts);
true
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Date-only timestamps fail to parse in version filtering

The VersionInfo::filter_by_date method only tries parsing timestamps with ts.parse::<Timestamp>(), which requires RFC3339 format. However, when falling back from versions host to native APIs, Node and Zig backends provide date-only strings like "2024-01-09" which cannot be parsed by Timestamp. These versions silently bypass the filter (returning true on parse failure). This is inconsistent with parse_into_timestamp in duration.rs which correctly handles date-only strings by parsing as jiff::civil::Date first. The --before filter will not work for Node and Zig when the versions host is unavailable.

Additional Locations (2)

Fix in Cursor Fix in Web

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Bug: Before-date filter not applied in resolve_sub function

The resolve_sub function receives opts: &ResolveOptions containing before_date but ignores it when resolving "latest". It calls backend.latest_version(config, None) instead of backend.latest_version_with_opts(config, None, opts.before_date). This means when using version subtraction syntax like mise install python@sub-0.1:latest --before 2024-01-01, the --before filter is silently ignored, and the base version is resolved from the unfiltered latest instead of the latest version before the specified date.

src/toolset/tool_version.rs#L254-L255

let v = match v {
"latest" => backend.latest_version(config, None).await?.unwrap(),

Fix in Cursor Fix in Web


Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 a --before flag that filters tool versions by release date, enabling reproducible builds by ensuring only versions released before a specific date are considered. This functionality is similar to npm's --before flag.

Key changes:

  • New --before CLI flag for install, upgrade, and use commands
  • New MISE_INSTALL_BEFORE setting (env: MISE_INSTALL_BEFORE)
  • Support for absolute dates (2024-06-01) and relative durations (90d, 1y)
  • Modified versions host endpoint to use .toml format for timestamp data
  • Updated core plugins to provide VersionInfo with timestamps instead of just version strings

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/backend/mod.rs Core filtering logic using VersionInfo::filter_by_date(), new list_versions_matching_with_opts() and latest_version_with_opts() methods
src/versions_host.rs Switch from plain text to TOML endpoint for timestamp data, simplified API to use tool name directly
src/duration.rs New parse_into_timestamp() function supporting multiple date/duration formats
src/cli/{install,upgrade,use}.rs Added --before flag and get_before_date() helper method
src/plugins/core/*.rs Updated plugins (node, bun, rust, swift, zig, elixir, erlang) to return VersionInfo with timestamps
src/toolset/tool_version.rs Updated resolution to pass before_date option through version matching calls
settings.toml New install_before setting with documentation

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

Comment thread docs/cli/upgrade.md Outdated

In "soft" mode (default), this only affects fuzzy version matches like "20" or "latest".
Explicitly pinned versions like "22.5.0" are not filtered.
Use `install_before_mode = "hard"` setting to filter all versions strictly.
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The documentation references an install_before_mode setting that doesn't exist in the codebase. This setting is not defined in settings.toml or implemented anywhere. Either remove this line or implement the setting.

Copilot uses AI. Check for mistakes.
Comment thread man/man1/mise.1 Outdated

In "soft" mode (default), this only affects fuzzy version matches like "20" or "latest".
Explicitly pinned versions like "22.5.0" are not filtered.
Use `install_before_mode = "hard"` setting to filter all versions strictly.
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The man page references an install_before_mode setting that doesn't exist in the codebase. This setting is not defined in settings.toml or implemented anywhere. Either remove this line or implement the setting.

Suggested change
Use `install_before_mode = "hard"` setting to filter all versions strictly.

Copilot uses AI. Check for mistakes.
Comment thread mise.usage.kdl Outdated
}
flag "-n --dry-run" help="Just print what would be done, don't actually do it"
flag --before help="Only upgrade to versions released before this date" {
long_help "Only upgrade to versions released before this date\n\nSupports absolute dates like \"2024-06-01\" and relative durations like \"90d\" or \"1y\".\nThis can be useful for reproducibility or security purposes.\n\nIn \"soft\" mode (default), this only affects fuzzy version matches like \"20\" or \"latest\".\nExplicitly pinned versions like \"22.5.0\" are not filtered.\nUse `install_before_mode = \"hard\"` setting to filter all versions strictly."
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The usage documentation references an install_before_mode setting that doesn't exist in the codebase. This setting is not defined in settings.toml or implemented anywhere. Either remove this line or implement the setting.

Suggested change
long_help "Only upgrade to versions released before this date\n\nSupports absolute dates like \"2024-06-01\" and relative durations like \"90d\" or \"1y\".\nThis can be useful for reproducibility or security purposes.\n\nIn \"soft\" mode (default), this only affects fuzzy version matches like \"20\" or \"latest\".\nExplicitly pinned versions like \"22.5.0\" are not filtered.\nUse `install_before_mode = \"hard\"` setting to filter all versions strictly."
long_help "Only upgrade to versions released before this date\n\nSupports absolute dates like \"2024-06-01\" and relative durations like \"90d\" or \"1y\".\nThis can be useful for reproducibility or security purposes.\n\nIn \"soft\" mode (default), this only affects fuzzy version matches like \"20\" or \"latest\".\nExplicitly pinned versions like \"22.5.0\" are not filtered."

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Dec 14, 2025

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2025.12.7 x -- echo 20.0 ± 0.4 19.5 25.5 1.00
mise x -- echo 20.3 ± 0.2 19.8 21.2 1.01 ± 0.02

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2025.12.7 env 19.6 ± 0.3 19.1 22.1 1.00
mise env 19.9 ± 0.6 19.4 25.4 1.02 ± 0.03

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2025.12.7 hook-env 19.7 ± 0.2 19.2 21.0 1.00
mise hook-env 19.9 ± 0.6 19.4 28.6 1.01 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2025.12.7 ls 16.5 ± 0.2 16.0 18.0 1.00
mise ls 16.8 ± 0.2 16.3 18.3 1.02 ± 0.02

xtasks/test/perf

Command mise-2025.12.7 mise Variance
install (cached) 107ms 109ms -1%
ls (cached) 64ms 65ms -1%
bin-paths (cached) 71ms 71ms +0%
task-ls (cached) 433ms 433ms +0%

@jdx
Copy link
Copy Markdown
Owner Author

jdx commented Dec 14, 2025

@copilot can you fix cursor's comment?

Copy link
Copy Markdown
Contributor

Copilot AI commented Dec 14, 2025

@jdx I've opened a new pull request, #7299, to work on those changes. Once the pull request is ready, I'll request review from you.

@jdx jdx changed the title feat(cli): add --before flag for date-based version filtering feat: add --before flag for date-based version filtering Dec 14, 2025
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Bug: Upgrade dry-run output ignores before-date filter

The upgrade command's --dry-run output doesn't reflect the --before filter. The list_outdated_versions call on line 80 determines the latest version without considering before_date, which is only applied later in InstallOptions on line 169. When --dry-run is used, the output shows "Would install {tool}@{o.latest}" using the unfiltered latest version. The actual upgrade (without dry-run) would correctly apply the --before filter and potentially install a different version, making the dry-run output misleading.

src/cli/upgrade.rs#L141-L157

mise/src/cli/upgrade.rs

Lines 141 to 157 in 9f6eaee

if self.dry_run {
for (o, current) in &to_remove {
miseprintln!("Would uninstall {}@{}", o.name, current);
}
for o in &outdated {
miseprintln!("Would install {}@{}", o.name, o.latest);
}
for (o, cf) in &config_file_updates {
miseprintln!(
"Would bump {}@{} in {}",
o.name,
o.tool_request.version(),
display_path(cf.get_path())
);
}
return Ok(());

src/cli/upgrade.rs#L79-L80

mise/src/cli/upgrade.rs

Lines 79 to 80 in 9f6eaee

.await?;
let mut outdated = ts.list_outdated_versions(&config, self.bump).await;

Fix in Cursor Fix in Web


@jdx
Copy link
Copy Markdown
Owner Author

jdx commented Dec 14, 2025

@copilot

Bug: Upgrade dry-run output ignores before-date filter
The upgrade command's --dry-run output doesn't reflect the --before filter. The list_outdated_versions call on line 80 determines the latest version without considering before_date, which is only applied later in InstallOptions on line 169. When --dry-run is used, the output shows "Would install {tool}@{o.latest}" using the unfiltered latest version. The actual upgrade (without dry-run) would correctly apply the --before filter and potentially install a different version, making the dry-run output misleading.

src/cli/upgrade.rs#L141-L15

Copy link
Copy Markdown
Contributor

Copilot AI commented Dec 14, 2025

@jdx I've opened a new pull request, #7300, to work on those changes. Once the pull request is ready, I'll request review from you.

Comment thread src/plugins/core/rust.rs Outdated
The resolve_sub function was calling backend.latest_version() without
the before_date filter, causing date-based version filtering to be
ignored when resolving "latest" versions with sub-version syntax.

Now uses latest_version_with_opts() with opts.before_date to properly
respect the install_before setting and --before CLI flag.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Owner Author

jdx commented Dec 15, 2025

I've fixed the bug identified in the review comments:

Bug: resolve_sub ignores before_date

The resolve_sub function was calling backend.latest_version(config, None) without the before_date filter when resolving "latest" versions. This caused date-based version filtering to be bypassed for sub-version syntax like sub-1:latest.

Fixed by changing to backend.latest_version_with_opts(config, None, opts.before_date).

This fix also addresses the "upgrade dry-run output" concern - the list_outdated_versions chain now correctly passes before_date through all resolution methods, so the dry-run output shows the correctly filtered latest version.

The e2e test confirms this is working correctly:

  • upgrade --before 2025-01-01 shows "Would install tiny@1.1.0" (the filtered version) rather than an unfiltered latest.

jdx and others added 2 commits December 14, 2025 19:46
1. Fix date-only timestamp parsing in filter_by_date
   - Use parse_into_timestamp() instead of direct Timestamp parsing
   - This handles date-only formats like "2024-01-09" from Node/Zig backends

2. Add before_date support to outdated_info trait method
   - Updated Backend::outdated_info signature to include ResolveOptions
   - Updated Rust plugin's outdated_info to use opts.before_date
   - Updated call site in list_outdated_versions to pass opts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Owner Author

jdx commented Dec 15, 2025

Additional fixes pushed

I've addressed the remaining PR review comments:

1. Date-only timestamp parsing in filter_by_date (src/backend/mod.rs)

  • Changed from ts.parse::<Timestamp>() to parse_into_timestamp(ts)
  • This now correctly handles date-only formats like "2024-01-09" from Node and Zig backends when versions host is unavailable

2. Rust plugin outdated_info ignoring before_date (multiple files)

  • Added opts: &ResolveOptions parameter to Backend::outdated_info trait method
  • Updated Rust plugin's implementation to use opts instead of ResolveOptions::default()
  • Updated call site in list_outdated_versions to pass the opts

All unit tests (377) and e2e tests pass.

Comment thread src/toolset/tool_version.rs Outdated
1. Fix potential panic in resolve_sub when date filter excludes all versions
   - Replace .unwrap() with .ok_or_else() for proper error handling
   - Provide descriptive error message mentioning date filter when applicable

2. Restore mise.toml that was accidentally removed in earlier commit

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread src/duration.rs
let now = Timestamp::now();
let past = now.checked_sub(span)?;
return Ok(past);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Negative duration inputs produce unexpected future timestamps

The parse_into_timestamp function doesn't validate that parsed spans are positive before subtracting from now. If a user accidentally provides a negative duration like -90d, jiff parses it as a negative span, and checked_sub on a negative value effectively adds, resulting in a future timestamp. This causes the --before filter to include all versions rather than filtering them, since all versions are released "before" a future date. The existing parse_duration function in the same file explicitly checks for and rejects negative durations, but this new function lacks that validation.

Fix in Cursor Fix in Web

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Bug: Pinned versions incorrectly filtered despite documentation claims

The documentation in settings.toml states that "Explicitly pinned versions like node@22.5.0 are not filtered", but the implementation does filter them. In resolve_version, the check for installed versions (lines 238-246) only runs when !opts.latest_versions. Since the install, upgrade, and use commands all set latest_versions: true, this check is skipped, and list_versions_matching_with_opts is called with before_date which applies the date filter to all versions including exact pinned ones. If a user has MISE_INSTALL_BEFORE set and tries to install an exact version released after that date that isn't already installed, the version won't be found because it's filtered out.

src/toolset/tool_version.rs#L246-L253

}
let matches = backend
.list_versions_matching_with_opts(config, &v, opts.before_date)
.await?;
if matches.contains(&v) {
return build(v);
}
Self::resolve_prefix(config, request, &v, opts).await

settings.toml#L597-L600

mise/settings.toml

Lines 597 to 600 in 5698c26

**Behavior**: This filter only applies when resolving fuzzy version requests like `node@20` or `latest`.
Explicitly pinned versions like `node@22.5.0` are not filtered, allowing you to selectively
use newer versions for specific tools while keeping others behind the cutoff date.

Fix in Cursor Fix in Web


autofix-ci Bot and others added 2 commits December 15, 2025 02:06
1. Validate negative duration inputs in parse_into_timestamp
   - Reject negative durations like "-90d" which would create future timestamps
   - Matches existing validation in parse_duration function

2. Preserve exact/pinned version matching regardless of date filter
   - Only apply date filter for fuzzy/prefix version resolution
   - Explicit versions like "22.5.0" are not filtered by --before flag
   - Optimized to only fetch unfiltered list when needed (date filter active
     and exact version not in filtered results)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread src/cli/upgrade.rs Outdated
jdx and others added 2 commits December 14, 2025 20:18
When using relative durations like "90d", each call to get_before_date()
computes a new timestamp using Timestamp::now(). This caused inconsistency
between the outdated version check and the actual installation.

Now computes the timestamp once at the start and passes it through to
both list_outdated_versions and InstallOptions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Owner Author

jdx commented Dec 15, 2025

bugbot run

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no bugs!


@jdx jdx enabled auto-merge (squash) December 15, 2025 02:39
@jdx jdx merged commit 9634472 into main Dec 15, 2025
29 checks passed
@jdx jdx deleted the install-before-feature branch December 15, 2025 02:40
jdx pushed a commit that referenced this pull request Dec 15, 2025
### 🚀 Features

- **(conda)** add dependency resolution for conda packages by @jdx in
[#7280](#7280)
- **(go)** add created_at support to ls-remote --json by @jdx in
[#7305](#7305)
- **(hook-env)** add hook_env.cache_ttl and hook_env.chpwd_only settings
for NFS optimization by @jdx in
[#7312](#7312)
- **(hooks)** add MISE_TOOL_NAME and MISE_TOOL_VERSION to
preinstall/postinstall hooks by @jdx in
[#7311](#7311)
- **(shell_alias)** add shell_alias support for cross-shell aliases by
@jdx in [#7316](#7316)
- **(tool)** add security field to mise tool --json by @jdx in
[#7303](#7303)
- add --before flag for date-based version filtering by @jdx in
[#7298](#7298)

### 🐛 Bug Fixes

- **(aqua)** support cosign v3 bundle verification by @jdx in
[#7314](#7314)
- **(config)** use correct config_root in tera context for hooks by @jdx
in [#7309](#7309)
- **(nu)** fix nushell deactivation script on Windows by @fu050409 in
[#7213](#7213)
- **(python)** apply uv_venv_create_args in auto-venv code path by @jdx
in [#7310](#7310)
- **(shell)** escape exe path in activation scripts for paths with
spaces by @jdx in [#7315](#7315)
- **(task)** parallelize exec_env loading to fix parallel task execution
by @jdx in [#7313](#7313)
- track downloads for python and java by @jdx in
[#7304](#7304)
- include full tool ID in download track by @jdx in
[#7320](#7320)

### 📚 Documentation

- Switch `postinstall` code to be shell-agnostic by @thejcannon in
[#7317](#7317)

### 🧪 Testing

- **(e2e)** disable debug mode by default for windows-e2e by @jdx in
[#7318](#7318)

### New Contributors

- @fu050409 made their first contribution in
[#7213](#7213)
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.

3 participants