feat(conda): replace custom backend with rattler crates#8325
Conversation
Replaces ~985 lines of custom code (version matching, recursive dep resolution via anaconda.org API, manual archive extraction) with the rattler Rust crates that power pixi. - rattler_repodata_gateway: fetches repodata from conda channels - rattler_solve (resolvo): proper SAT-based dependency solving - rattler_package_streaming: extracts .conda/.tar.bz2 archives - rattler_virtual_packages: detects system virtual packages (__glibc, etc.) - rattler_conda_types: shared types (Channel, MatchSpec, Platform, etc.) Keeps platform-specific binary patching (patchelf/install_name_tool) and text prefix fixing unchanged, as rattler's link_package assumes a full conda environment layout incompatible with mise's extract-to-path model. The CondaPackageInfo lockfile interface is preserved unchanged. Co-Authored-By: Claude Sonnet 4.6 <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 significantly overhauls the experimental Conda backend by replacing its custom implementation with the 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.
Code Review
The pull request replaces the custom conda backend with the rattler Rust crates, which significantly refactors the conda integration. This change improves dependency resolution, package extraction, and virtual package detection by leveraging a more robust and actively maintained library. The changes to Cargo.lock and Cargo.toml reflect the new dependencies. The core logic in src/backend/conda.rs has been updated to use rattler's functionalities for fetching repodata, solving packages, and extracting archives, replacing the previous custom implementations. The _list_remote_versions and resolve_lock_info methods have been adapted to interact with the new rattler components. The install_version_ method now differentiates between fresh installs (solving dependencies) and locked installs (using pre-defined URLs). Overall, this is a substantial and positive change that moves the experimental conda backend towards a more stable and feature-rich foundation.
| native-tls = [ | ||
| "gix/blocking-http-transport-reqwest-native-tls", | ||
| "reqwest/native-tls", | ||
| "rattler_repodata_gateway/native-tls", |
| rustls = [ | ||
| "gix/blocking-http-transport-reqwest-rust-tls", | ||
| "reqwest/rustls-tls", | ||
| "rattler_repodata_gateway/rustls-tls", |
| rustls-native-roots = [ | ||
| "gix/blocking-http-transport-reqwest-rust-tls", | ||
| "reqwest/rustls-tls-native-roots", | ||
| "rattler_repodata_gateway/rustls-tls", |
| fn channel(&self) -> Result<Channel> { | ||
| let name = self.channel_name(); | ||
| let config = ChannelConfig::default_with_root_dir(std::path::PathBuf::from("/")); | ||
| Channel::from_str(&name, &config) | ||
| .map_err(|e| eyre::eyre!("invalid conda channel '{}': {}", name, e)) |
| fn url_filename(url: &url::Url) -> String { | ||
| url.path_segments() | ||
| .and_then(|s| s.last()) | ||
| .unwrap_or("package") | ||
| .to_string() |
There was a problem hiding this comment.
The url_filename function is used to extract the filename from a URL. While unwrap_or("package") provides a fallback, it might be more robust to return a Result or handle the None case more explicitly if url.path_segments() could genuinely be empty or malformed in a way that last() returns None for valid URLs.
src/backend/conda.rs
Outdated
| #[cfg(any(target_os = "macos", target_os = "linux"))] | ||
| conda_common::fix_text_prefixes(&install_path); |
src/backend/conda.rs
Outdated
| #[cfg(any(target_os = "macos", target_os = "linux"))] | ||
| platform::fix_library_paths(ctx, &install_path)?; |
src/backend/conda.rs
Outdated
| #[cfg(any(target_os = "macos", target_os = "linux"))] | ||
| conda_common::fix_text_prefixes(&install_path); |
| let match_spec = MatchSpec::from_str(&tool_name, ParseStrictness::Lenient) | ||
| .map_err(|e| eyre::eyre!("invalid match spec for '{}': {}", tool_name, e))?; |
There was a problem hiding this comment.
| let match_spec = match MatchSpec::from_str(&spec_str, ParseStrictness::Lenient) { | ||
| Ok(s) => s, |
There was a problem hiding this comment.
There was a problem hiding this comment.
Pull request overview
This PR replaces the experimental Conda backend’s custom package metadata fetching, dependency resolution, and archive extraction logic with the rattler crate ecosystem (repodata gateway + SAT solver + streaming extractor + virtual package detection), while keeping existing post-extraction patching behavior.
Changes:
- Reworked conda backend to solve dependencies via
rattler_repodata_gateway+rattler_solveand extract archives viarattler_package_streaming. - Added
rattler_*crate dependencies and wired TLS feature flags forrattler_repodata_gateway. - Simplified lockfile dependency handling to store URLs/checksums derived from repodata records.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
src/backend/conda.rs |
Replaces custom API-based resolution/extraction with rattler repodata + SAT solve + extraction; updates lockfile integration. |
Cargo.toml |
Adds rattler_* crates and forwards TLS feature flags for the gateway crate. |
Cargo.lock |
Locks transitive dependencies introduced by the new rattler crates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Fetch repodata and solve the conda environment for the given specs and platform. | ||
| async fn solve_packages( | ||
| &self, | ||
| specs: Vec<MatchSpec>, | ||
| platform: CondaPlatform, | ||
| ) -> Result<Vec<RepoDataRecord>> { | ||
| let channel = self.channel()?; | ||
| let gateway = Self::create_gateway(); | ||
|
|
||
| let repodata: Vec<RepoData> = gateway | ||
| .query([channel], [platform, CondaPlatform::NoArch], specs.clone()) | ||
| .recursive(true) | ||
| .await | ||
| .map_err(|e| eyre::eyre!("failed to fetch repodata: {}", e))?; |
There was a problem hiding this comment.
solve_packages uses rattler_repodata_gateway directly, which bypasses the project-wide offline guard in crate::http (see HTTP.get_async_with_headers / Settings::offline()). In offline mode this backend will still attempt network access. Consider adding an explicit ensure!(!Settings::get().offline(), "offline mode is enabled") (or equivalent) before querying the gateway.
src/backend/conda.rs
Outdated
| platform_to_conda_subdir(OS.as_str(), ARCH.as_str()) | ||
| fn channel(&self) -> Result<Channel> { | ||
| let name = self.channel_name(); | ||
| let config = ChannelConfig::default_with_root_dir(std::path::PathBuf::from("/")); |
There was a problem hiding this comment.
ChannelConfig::default_with_root_dir(PathBuf::from("/")) hard-codes / as the base for resolving relative channels (e.g. ./local-channel, file:...) and is also not a valid root on Windows. Use a real root dir such as the current working directory (preferred) or dirs::HOME, so relative channels resolve predictably on all platforms.
| let config = ChannelConfig::default_with_root_dir(std::path::PathBuf::from("/")); | |
| let root_dir = std::env::current_dir().unwrap_or_else(|_| dirs::HOME.clone()); | |
| let config = ChannelConfig::default_with_root_dir(root_dir); |
| /// Download a single package archive to the shared conda data dir. | ||
| async fn download_record(record: RepoDataRecord) -> Result<PathBuf> { | ||
| let url_str = record.url.to_string(); | ||
| let filename = Self::url_filename(&record.url); | ||
| let dest = Self::conda_data_dir().join(&filename); | ||
|
|
||
| // Handle compound specs like ">=1.0,<2.0" by splitting on comma | ||
| if spec.contains(',') { | ||
| return spec | ||
| .split(',') | ||
| .all(|part| Self::version_matches(version, part.trim())); | ||
| if dest.exists() { | ||
| return Ok(dest); | ||
| } | ||
|
|
||
| // Comparison operators (>=, <=, >, <, ==, !=) | ||
| Self::check_version_constraint(version, spec) | ||
| file::create_dir_all(&Self::conda_data_dir())?; | ||
| HTTP.download_file(&url_str, &dest, None).await?; | ||
| Ok(dest) |
There was a problem hiding this comment.
Downloads are not verified against the sha256 provided by repodata/lockfile. This is an integrity regression: a corrupted/partial file in conda_data_dir will be silently reused (because of dest.exists()), and locked installs won't be deterministic. Consider verifying record.package_record.sha256 after download (and re-downloading on mismatch), and also verifying existing cached files before reusing them.
src/backend/conda.rs
Outdated
| let dep_basenames = platform_info.conda_deps.clone().unwrap_or_default(); | ||
| let lockfile = self.read_lockfile_for_tool(tv)?; | ||
|
|
||
| // Check if file already exists with valid checksum | ||
| if tarball_path.exists() { | ||
| if Self::verify_checksum(&tarball_path, pkg.sha256.as_deref()).is_ok() { | ||
| return Ok(tarball_path); | ||
| // Collect dep URLs from lockfile (deps first, main last) | ||
| let mut urls: Vec<String> = vec![]; | ||
| for basename in &dep_basenames { | ||
| if let Some(pkg_info) = lockfile.get_conda_package(platform_key, basename) { | ||
| urls.push(pkg_info.url.clone()); | ||
| } else { | ||
| warn!( | ||
| "conda package {} not found in lockfile for {}", | ||
| basename, platform_key | ||
| ); | ||
| } | ||
| // Corrupted file - delete it | ||
| let _ = std::fs::remove_file(&tarball_path); | ||
| } | ||
| urls.push(main_url); | ||
|
|
||
| // Download to a temp file first, then rename after verification | ||
| // This ensures the final path never contains a corrupted file | ||
| let temp_path = tarball_path.with_extension(format!( | ||
| "{}.tmp.{}", | ||
| tarball_path | ||
| .extension() | ||
| .and_then(|e| e.to_str()) | ||
| .unwrap_or(""), | ||
| std::process::id() | ||
| )); | ||
|
|
||
| // Clean up any stale temp file from previous runs | ||
| let _ = std::fs::remove_file(&temp_path); | ||
|
|
||
| HTTP.download_file(&pkg.download_url, &temp_path, None) | ||
| .await | ||
| .wrap_err_with(|| format!("failed to download {}", pkg.download_url))?; | ||
|
|
||
| // Verify checksum of downloaded file | ||
| let file_size = std::fs::metadata(&temp_path).map(|m| m.len()).unwrap_or(0); | ||
| Self::verify_checksum(&temp_path, pkg.sha256.as_deref()).wrap_err_with(|| { | ||
| format!( | ||
| "checksum verification failed for {} (file size: {} bytes)", | ||
| pkg.name, file_size | ||
| ) | ||
| })?; | ||
| ctx.pr | ||
| .set_message(format!("downloading {} packages", urls.len())); | ||
| let downloaded = parallel::parallel(urls, Self::download_url).await?; |
There was a problem hiding this comment.
Locked installs currently ignore per-package checksums stored in the lockfile (both for deps and the main package). To make the locked path actually reproducible, look up each package's checksum in the lockfile and verify it after download; if the checksum is missing, consider failing or at least not reusing an existing cached file without verification.
src/backend/conda.rs
Outdated
| warn!( | ||
| "conda package {} not found in lockfile for {}", | ||
| basename, platform_key | ||
| ); |
There was a problem hiding this comment.
In the locked install path, if a basename listed in platform_info.conda_deps is missing from the lockfile's [conda-packages] section, the code only logs a warning and continues. That can produce an incomplete/incorrect environment while still claiming to be using locked dependencies. It would be safer to return an error when a referenced locked package cannot be found.
| warn!( | |
| "conda package {} not found in lockfile for {}", | |
| basename, platform_key | |
| ); | |
| return Err(eyre::eyre!( | |
| "conda package {} not found in lockfile for {}", | |
| basename, | |
| platform_key | |
| )); |
src/backend/conda.rs
Outdated
| .and_then(|p| p.conda_deps.as_ref()) | ||
| .is_some(); |
There was a problem hiding this comment.
has_locked is determined solely by the presence of conda_deps. If a tool has zero deps (so conda_deps is None) but the lockfile still contains a locked url/checksum for the main package, this will incorrectly take the fresh-solve path every time. Consider using platform_info.url.is_some() (or a similar check) to decide whether the lockfile should be used, independent of whether there are deps.
| .and_then(|p| p.conda_deps.as_ref()) | |
| .is_some(); | |
| .map(|p| p.url.is_some() || p.conda_deps.as_ref().is_some()) | |
| .unwrap_or(false); |
…link_package Use rattler's link_package() for proper conda prefix replacement instead of custom patchelf/install_name_tool invocations. This handles both text and binary prefix replacement correctly, including codesigning on macOS. Removes conda_common.rs, conda_linux.rs, conda_macos.rs (-638 lines). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.19 x -- echo |
20.7 ± 1.0 | 18.7 | 31.9 | 1.00 |
mise x -- echo |
23.1 ± 1.4 | 20.9 | 36.5 | 1.12 ± 0.08 |
x -- echo is 12% |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.19 env |
20.0 ± 1.0 | 18.3 | 35.7 | 1.00 |
mise env |
21.9 ± 0.7 | 20.0 | 24.4 | 1.10 ± 0.07 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.19 hook-env |
19.7 ± 0.4 | 18.7 | 23.1 | 1.00 |
mise hook-env |
22.2 ± 1.2 | 19.9 | 34.8 | 1.12 ± 0.06 |
hook-env is 12% |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.19 ls |
18.7 ± 0.8 | 17.0 | 27.8 | 1.00 |
mise ls |
21.3 ± 0.8 | 19.5 | 32.0 | 1.14 ± 0.06 |
ls is 14% |
xtasks/test/perf
| Command | mise-2026.2.19 | mise | Variance |
|---|---|---|---|
| install (cached) | 99ms | -20% | |
| ls (cached) | 67ms | 75ms | -10% |
| bin-paths (cached) | 69ms | 77ms | -10% |
| task-ls (cached) | 2678ms | ✅ 758ms | +253% |
✅ Performance improvement: task-ls cached is 253%
- Fix cross-platform virtual packages: use detect_for_platform() so lockfile generation on macOS correctly uses __glibc/__linux for Linux targets instead of __osx - Fix zero-dep packages bypassing lockfile: check url.is_some() instead of conda_deps.is_some(), and always store conda_deps (even if empty) - Error instead of warn when locked dep is missing from lockfile - Use cwd instead of hardcoded "/" for ChannelConfig root dir Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verify SHA256 checksums after downloading conda packages and before reusing cached files. Uses atomic writes (download to .tmp, verify, rename) to prevent partial/corrupt files from being cached. Both fresh solves (checksum from repodata) and locked installs (checksum from lockfile) are verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| size: None, | ||
| url_api: None, | ||
| conda_deps: Some(dep_basenames), | ||
| }), |
There was a problem hiding this comment.
Lockfile conda_deps always Some, even when empty
Low Severity
In resolve_lock_info, conda_deps is set to Some(dep_basenames) even when the list is empty, whereas the old code explicitly checked if conda_deps.is_empty() { None } else { Some(conda_deps) }. This causes unnecessary conda_deps = [] entries in lockfiles for packages that have no dependencies, adding noise to lockfile diffs. While consuming code handles both None and Some([]) identically via unwrap_or_default(), the inconsistency with the pattern used in install_fresh (which stores Some(dep_basenames.clone()) without the check) means lockfile output differs from what the old code produced.
Conda packages on Windows can place binaries in either `bin/` (MSVC builds) or `Library/bin/` (MSYS2/MinGW builds). The rattler solver may pick a different build variant than the old custom resolver, so we need to include both paths to ensure binaries are found regardless of which build variant is installed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| } else { | ||
| bail!("unsupported conda package format: {}", filename); | ||
| file::create_dir_all(Self::conda_data_dir())?; | ||
| let temp = dest.with_extension("tmp"); |
There was a problem hiding this comment.
Temp file lacks unique suffix, enabling concurrent write races
Medium Severity
The download_to method creates a temp file using dest.with_extension("tmp"), producing a fixed temp path per destination. The old code included std::process::id() in the temp filename to prevent concurrent processes from clobbering each other's downloads. Without a unique suffix (e.g., PID or random), two mise processes downloading the same package simultaneously will write to the same temp file, risking data corruption before the atomic rename.
| Ok(tarball_path) | ||
| Self::make_bins_executable(&install_path)?; | ||
|
|
||
| Ok(()) |
There was a problem hiding this comment.
Locked install path drops conda package data for lockfile
Medium Severity
install_from_locked takes tv as an immutable reference (&ToolVersion), so it cannot populate tv.conda_packages. The old code populated tv.conda_packages in both the locked and unlocked paths (the old comment explicitly stated "Store resolved packages in tv.conda_packages for lockfile update"). In the fresh path, install_fresh correctly writes to tv.conda_packages, but in the locked path this data is left empty. If any downstream code relies on tv.conda_packages to persist the shared [conda-packages] lockfile section after install, the locked path would silently lose those entries.
Additional Locations (1)
## Summary Replaces the experimental conda backend's ~1,625 lines of custom code with the [rattler](https://github.com/conda/rattler) Rust crates — the same crates that power [pixi](https://github.com/prefix-dev/pixi). Motivated by [jdx#8318](jdx#8318) from a conda maintainer who noted the backend was directly using the unsupported `api.anaconda.org` API. **What was replaced:** - Custom version matching and comparison logic - Recursive dependency resolution via raw anaconda.org API calls - Manual `.conda` ZIP+tar.zst and `.tar.bz2` extraction - Hardcoded `SKIP_PACKAGES` list for virtual packages - Custom patchelf/install_name_tool/codesign binary patching (`conda_common.rs`, `conda_linux.rs`, `conda_macos.rs` deleted) **With rattler:** - `rattler_repodata_gateway` — fetches repodata from conda channels (proper CDN-cached repodata.json) - `rattler_solve` (resolvo) — SAT-based dependency solver, same as used by pixi/conda-libmamba - `rattler_package_streaming` — extracts `.conda` and `.tar.bz2` archives - `rattler_virtual_packages` — detects system virtual packages (`__glibc`, `__osx`, etc.) properly - `rattler::install::link_package` — proper conda installation with text AND binary prefix replacement, file permissions, and macOS codesigning **Key fix:** The old code explicitly skipped binary files during prefix replacement (checking for null bytes). `link_package` correctly handles binary prefix replacement by padding with null bytes to preserve byte length, which is how conda is designed to work. **Kept unchanged:** - `lockfile.rs` — `CondaPackageInfo` type and `[conda-packages]` TOML format - All registry entries and settings Since `EXPERIMENTAL = true`, breaking changes are acceptable. ## Test plan - [x] `cargo build` — compiles cleanly - [x] `mise run test:unit` — 481 tests pass - [x] `mise run lint` — all checks pass - [x] `mise run test:e2e test_conda`: - `conda:ruff@0.8.0` — installs with 24 deps, runs correctly - `conda:bat@0.24.0` — installs with 4 deps, runs correctly - `conda:jq@1.7.1` — installs with deps (oniguruma), runs correctly - `conda:postgresql@17.2` — installs with 29 deps, runs correctly - [x] `mise lock` — lockfile generated correctly with per-platform deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Large rewrite of conda install/solve behavior (networking, dependency resolution, and prefix handling) that could change which packages get installed or how they’re linked; mitigated somewhat by being confined to the experimental conda backend and adding checksum-verified, lockfile-driven installs. > > **Overview** > Switches the experimental `conda` backend from bespoke anaconda.org API calls, ad-hoc version/dependency selection, and manual archive extraction/prefix patching to the `rattler` ecosystem: `rattler_repodata_gateway` for repodata retrieval/caching, `rattler_solve` (resolvo) for dependency solving with virtual package detection, and `rattler::install::link_package` + `rattler_package_streaming` for extraction and correct text/binary prefix replacement. > > Lockfile behavior is preserved but now populated from solver `RepoDataRecord`s (URLs + sha256) and can perform deterministic installs by downloading the locked URL set with checksum verification; Windows bin path discovery is broadened to include both `Library/bin` and `bin`. The previous platform-specific patching modules (`conda_common.rs`, `conda_linux.rs`, `conda_macos.rs`) are removed, and Cargo dependencies/lockfile are updated to include the new `rattler*` crates and transitive deps. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 21d3602. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
|
Thank you @jdx! |
|
I should be thanking you! |
### 🚀 Features - **(conda)** replace custom backend with rattler crates by @jdx in [#8325](#8325) - **(task)** enforce per-task timeout configuration by @tvararu in [#8250](#8250) - **(vsix)** added vsix archives to http backend by @sosumappu in [#8306](#8306) - add core dotnet plugin for .NET SDK management by @jdx in [#8326](#8326) ### 🐛 Bug Fixes - **(conda)** preserve conda_packages on locked install and fix temp file race by @jdx in [#8335](#8335) - **(conda)** deduplicate repodata records to fix solver error on Linux by @jdx in [#8337](#8337) - **(env)** include watch_files in fast-path early exit check by @jdx in [#8317](#8317) - **(env)** clear fish completions when setting/unsetting shell aliases by @jdx in [#8324](#8324) - **(lockfile)** prevent lockfile writes when --locked is set by @jdx in [#8308](#8308) - **(lockfile)** prune orphan tool entries on mise lock by @mackwic in [#8265](#8265) - **(lockfile)** error on contradictory locked=true + lockfile=false config by @jdx in [#8329](#8329) - **(regal)** Update package location by @charlieegan3 in [#8315](#8315) - **(release)** strip markdown heading prefix from communique release title by @jdx in [#8303](#8303) - **(schema)** enforce additionalProperties constraint for env by @adamliang0 in [#8328](#8328) ### 📚 Documentation - Remove incorrect oh-my-zsh plugin ordering comment by @bvosk in [#8323](#8323) - require AI disclosure on GitHub comments by @jdx in [#8330](#8330) ### 📦 Registry - add `oxfmt` by @taoufik07 in [#8316](#8316) ### New Contributors - @adamliang0 made their first contribution in [#8328](#8328) - @tvararu made their first contribution in [#8250](#8250) - @bvosk made their first contribution in [#8323](#8323) - @taoufik07 made their first contribution in [#8316](#8316) - @charlieegan3 made their first contribution in [#8315](#8315) - @sosumappu made their first contribution in [#8306](#8306) ## 📦 Aqua Registry Updates #### New Packages (3) - [`Tyrrrz/FFmpegBin`](https://github.com/Tyrrrz/FFmpegBin) - [`elixir-lang/expert`](https://github.com/elixir-lang/expert) - [`erikjuhani/basalt`](https://github.com/erikjuhani/basalt) #### Updated Packages (5) - [`caarlos0/fork-cleaner`](https://github.com/caarlos0/fork-cleaner) - [`firecow/gitlab-ci-local`](https://github.com/firecow/gitlab-ci-local) - [`jackchuka/mdschema`](https://github.com/jackchuka/mdschema) - [`kunobi-ninja/kunobi-releases`](https://github.com/kunobi-ninja/kunobi-releases) - [`peco/peco`](https://github.com/peco/peco)
|
@jdx Well, the alternative would have been to block requests by mise at the repo level since you were not following the conda standards and misunderstood how it worked (or your agents?), and would have led to a suboptimal user experience. No hard feelings! Fortunately for us, @wolfv and the folks at @prefix-dev had already implemented part of the conda specs in the rattler crates and kindly donated it to the conda OSS organization, which I very much appreciate because of exactly these types of cases of downstream projects. Thank you for taking my feedback and running with it since it will make things work better for users |
## Summary Replaces the experimental conda backend's ~1,625 lines of custom code with the [rattler](https://github.com/conda/rattler) Rust crates — the same crates that power [pixi](https://github.com/prefix-dev/pixi). Motivated by [jdx#8318](jdx#8318) from a conda maintainer who noted the backend was directly using the unsupported `api.anaconda.org` API. **What was replaced:** - Custom version matching and comparison logic - Recursive dependency resolution via raw anaconda.org API calls - Manual `.conda` ZIP+tar.zst and `.tar.bz2` extraction - Hardcoded `SKIP_PACKAGES` list for virtual packages - Custom patchelf/install_name_tool/codesign binary patching (`conda_common.rs`, `conda_linux.rs`, `conda_macos.rs` deleted) **With rattler:** - `rattler_repodata_gateway` — fetches repodata from conda channels (proper CDN-cached repodata.json) - `rattler_solve` (resolvo) — SAT-based dependency solver, same as used by pixi/conda-libmamba - `rattler_package_streaming` — extracts `.conda` and `.tar.bz2` archives - `rattler_virtual_packages` — detects system virtual packages (`__glibc`, `__osx`, etc.) properly - `rattler::install::link_package` — proper conda installation with text AND binary prefix replacement, file permissions, and macOS codesigning **Key fix:** The old code explicitly skipped binary files during prefix replacement (checking for null bytes). `link_package` correctly handles binary prefix replacement by padding with null bytes to preserve byte length, which is how conda is designed to work. **Kept unchanged:** - `lockfile.rs` — `CondaPackageInfo` type and `[conda-packages]` TOML format - All registry entries and settings Since `EXPERIMENTAL = true`, breaking changes are acceptable. ## Test plan - [x] `cargo build` — compiles cleanly - [x] `mise run test:unit` — 481 tests pass - [x] `mise run lint` — all checks pass - [x] `mise run test:e2e test_conda`: - `conda:ruff@0.8.0` — installs with 24 deps, runs correctly - `conda:bat@0.24.0` — installs with 4 deps, runs correctly - `conda:jq@1.7.1` — installs with deps (oniguruma), runs correctly - `conda:postgresql@17.2` — installs with 29 deps, runs correctly - [x] `mise lock` — lockfile generated correctly with per-platform deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Large rewrite of conda install/solve behavior (networking, dependency resolution, and prefix handling) that could change which packages get installed or how they’re linked; mitigated somewhat by being confined to the experimental conda backend and adding checksum-verified, lockfile-driven installs. > > **Overview** > Switches the experimental `conda` backend from bespoke anaconda.org API calls, ad-hoc version/dependency selection, and manual archive extraction/prefix patching to the `rattler` ecosystem: `rattler_repodata_gateway` for repodata retrieval/caching, `rattler_solve` (resolvo) for dependency solving with virtual package detection, and `rattler::install::link_package` + `rattler_package_streaming` for extraction and correct text/binary prefix replacement. > > Lockfile behavior is preserved but now populated from solver `RepoDataRecord`s (URLs + sha256) and can perform deterministic installs by downloading the locked URL set with checksum verification; Windows bin path discovery is broadened to include both `Library/bin` and `bin`. The previous platform-specific patching modules (`conda_common.rs`, `conda_linux.rs`, `conda_macos.rs`) are removed, and Cargo dependencies/lockfile are updated to include the new `rattler*` crates and transitive deps. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 21d3602. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### 🚀 Features - **(conda)** replace custom backend with rattler crates by @jdx in [jdx#8325](jdx#8325) - **(task)** enforce per-task timeout configuration by @tvararu in [jdx#8250](jdx#8250) - **(vsix)** added vsix archives to http backend by @sosumappu in [jdx#8306](jdx#8306) - add core dotnet plugin for .NET SDK management by @jdx in [jdx#8326](jdx#8326) ### 🐛 Bug Fixes - **(conda)** preserve conda_packages on locked install and fix temp file race by @jdx in [jdx#8335](jdx#8335) - **(conda)** deduplicate repodata records to fix solver error on Linux by @jdx in [jdx#8337](jdx#8337) - **(env)** include watch_files in fast-path early exit check by @jdx in [jdx#8317](jdx#8317) - **(env)** clear fish completions when setting/unsetting shell aliases by @jdx in [jdx#8324](jdx#8324) - **(lockfile)** prevent lockfile writes when --locked is set by @jdx in [jdx#8308](jdx#8308) - **(lockfile)** prune orphan tool entries on mise lock by @mackwic in [jdx#8265](jdx#8265) - **(lockfile)** error on contradictory locked=true + lockfile=false config by @jdx in [jdx#8329](jdx#8329) - **(regal)** Update package location by @charlieegan3 in [jdx#8315](jdx#8315) - **(release)** strip markdown heading prefix from communique release title by @jdx in [jdx#8303](jdx#8303) - **(schema)** enforce additionalProperties constraint for env by @adamliang0 in [jdx#8328](jdx#8328) ### 📚 Documentation - Remove incorrect oh-my-zsh plugin ordering comment by @bvosk in [jdx#8323](jdx#8323) - require AI disclosure on GitHub comments by @jdx in [jdx#8330](jdx#8330) ### 📦 Registry - add `oxfmt` by @taoufik07 in [jdx#8316](jdx#8316) ### New Contributors - @adamliang0 made their first contribution in [jdx#8328](jdx#8328) - @tvararu made their first contribution in [jdx#8250](jdx#8250) - @bvosk made their first contribution in [jdx#8323](jdx#8323) - @taoufik07 made their first contribution in [jdx#8316](jdx#8316) - @charlieegan3 made their first contribution in [jdx#8315](jdx#8315) - @sosumappu made their first contribution in [jdx#8306](jdx#8306) ## 📦 Aqua Registry Updates #### New Packages (3) - [`Tyrrrz/FFmpegBin`](https://github.com/Tyrrrz/FFmpegBin) - [`elixir-lang/expert`](https://github.com/elixir-lang/expert) - [`erikjuhani/basalt`](https://github.com/erikjuhani/basalt) #### Updated Packages (5) - [`caarlos0/fork-cleaner`](https://github.com/caarlos0/fork-cleaner) - [`firecow/gitlab-ci-local`](https://github.com/firecow/gitlab-ci-local) - [`jackchuka/mdschema`](https://github.com/jackchuka/mdschema) - [`kunobi-ninja/kunobi-releases`](https://github.com/kunobi-ninja/kunobi-releases) - [`peco/peco`](https://github.com/peco/peco)


Summary
Replaces the experimental conda backend's ~1,625 lines of custom code with the rattler Rust crates — the same crates that power pixi. Motivated by #8318 from a conda maintainer who noted the backend was directly using the unsupported
api.anaconda.orgAPI.What was replaced:
.condaZIP+tar.zst and.tar.bz2extractionSKIP_PACKAGESlist for virtual packagesconda_common.rs,conda_linux.rs,conda_macos.rsdeleted)With rattler:
rattler_repodata_gateway— fetches repodata from conda channels (proper CDN-cached repodata.json)rattler_solve(resolvo) — SAT-based dependency solver, same as used by pixi/conda-libmambarattler_package_streaming— extracts.condaand.tar.bz2archivesrattler_virtual_packages— detects system virtual packages (__glibc,__osx, etc.) properlyrattler::install::link_package— proper conda installation with text AND binary prefix replacement, file permissions, and macOS codesigningKey fix: The old code explicitly skipped binary files during prefix replacement (checking for null bytes).
link_packagecorrectly handles binary prefix replacement by padding with null bytes to preserve byte length, which is how conda is designed to work.Kept unchanged:
lockfile.rs—CondaPackageInfotype and[conda-packages]TOML formatSince
EXPERIMENTAL = true, breaking changes are acceptable.Test plan
cargo build— compiles cleanlymise run test:unit— 481 tests passmise run lint— all checks passmise run test:e2e test_conda:conda:ruff@0.8.0— installs with 24 deps, runs correctlyconda:bat@0.24.0— installs with 4 deps, runs correctlyconda:jq@1.7.1— installs with deps (oniguruma), runs correctlyconda:postgresql@17.2— installs with 29 deps, runs correctlymise lock— lockfile generated correctly with per-platform deps🤖 Generated with Claude Code
Note
Medium Risk
Large rewrite of conda install/solve behavior (networking, dependency resolution, and prefix handling) that could change which packages get installed or how they’re linked; mitigated somewhat by being confined to the experimental conda backend and adding checksum-verified, lockfile-driven installs.
Overview
Switches the experimental
condabackend from bespoke anaconda.org API calls, ad-hoc version/dependency selection, and manual archive extraction/prefix patching to therattlerecosystem:rattler_repodata_gatewayfor repodata retrieval/caching,rattler_solve(resolvo) for dependency solving with virtual package detection, andrattler::install::link_package+rattler_package_streamingfor extraction and correct text/binary prefix replacement.Lockfile behavior is preserved but now populated from solver
RepoDataRecords (URLs + sha256) and can perform deterministic installs by downloading the locked URL set with checksum verification; Windows bin path discovery is broadened to include bothLibrary/binandbin. The previous platform-specific patching modules (conda_common.rs,conda_linux.rs,conda_macos.rs) are removed, and Cargo dependencies/lockfile are updated to include the newrattler*crates and transitive deps.Written by Cursor Bugbot for commit 21d3602. This will update automatically on new commits. Configure here.