Skip to content

fix(backend): improve conda patchelf and dependency resolution for complex packages#8087

Merged
jdx merged 15 commits intomainfrom
registry/conda-complex-tools
Feb 11, 2026
Merged

fix(backend): improve conda patchelf and dependency resolution for complex packages#8087
jdx merged 15 commits intomainfrom
registry/conda-complex-tools

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 10, 2026

Summary

  • Improve Linux library path fixing (patchelf RPATH) for conda packages with large dependency trees
  • Pin conda dependency versions from build strings to install the correct library versions
  • Add conda backends for ffmpeg, ghc, and vim

Changes to conda_linux.rs

Comprehensive RPATH

Previously, RPATH was set to only $ORIGIN/../lib for binaries and $ORIGIN for libraries. Complex packages like ffmpeg have shared libraries spread across multiple subdirectories. Now we:

  • Discover all directories containing .so files (using precise .so/.so. matching)
  • Build RPATH strings that include all library directories using $ORIGIN-relative paths

Interpreter fixing

If a conda binary has a hardcoded ELF interpreter pointing to a conda build path (e.g. /home/conda/feedstock_root/.../ld-linux-x86-64.so.2), the binary can't load at all (exit code 127). Now we:

  • Detect conda build-time interpreter paths
  • Replace with a local linker from the install dir if available, or fall back to the system linker
  • Support both x86-64 and aarch64 architectures

Error logging

Previously, all patchelf errors were silently swallowed (let _ = ...). Now they are logged at debug level for troubleshooting, including spawn failures.

Performance

Only call fix_interpreter for files in bin//libexec/, skipping shared libraries that don't have PT_INTERP.

Changes to conda.rs - Build string version pinning

Problem

Conda build strings encode which dependency versions a package was built against (e.g., py310 = Python 3.10, pl5321 = Perl 5.32). However, the dependency resolver was ignoring this and installing the latest version matching a loose spec like >=3.10, which would pick Python 3.15 instead of 3.10.

Fix

Parse build string prefixes (py, pl) to extract version pins, then use them to constrain dependency resolution. Falls back to the original version spec if the pinned version isn't available.

Example: vim package vim-9.1.1858-py310pl5321h03900fa_0 now correctly gets Python 3.10.* instead of 3.15.

Registry updates

Tool conda package Test command
ffmpeg ffmpeg ffmpeg -version
ghc ghc ghc --version
vim vim vim --version

Context

These tools were removed from #8083 because they failed with exit code 127 (shared library resolution failure). This PR improves both the patchelf mechanism and the dependency resolver to handle their complex dependency trees.

Test plan

  • mise run lint passes
  • mise run build compiles successfully
  • mise x conda:vim -- vim --version works locally (installs Python 3.10 correctly)
  • CI mise test-tool for ffmpeg — passed
  • CI mise test-tool for ghc — passed
  • CI mise test-tool for vim — pending

🤖 Generated with Claude Code


Note

Medium Risk
Touches conda install/dependency resolution and Linux binary patching (patchelf), which can affect runtime behavior across many packages; changes are targeted but broad in impact if the heuristics mis-detect pins/prefixes or generate incorrect RPATH/interpreter settings.

Overview
Improves conda-based installs for complex Linux packages by making patchelf fixes more robust: builds RPATHs from all discovered shared-library directories (excluding sysroots), optionally rewrites hardcoded ELF interpreters pointing at conda build paths, and logs patchelf failures instead of silently swallowing them.

Enhances conda dependency resolution to honor build-string version pins (e.g. py310 → Python 3.10) and stops skipping runtime python/perl/ruby packages so their shared libs can be present when tools link against them; also replaces conda placeholder prefixes in extracted text files.

Adds conda: as a backend option for ffmpeg, ghc, and vim in the registry (with a new ffmpeg smoke test), and improves mise test-tool diagnostics by printing captured output for exit code 127.

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

Improve Linux library path fixing for conda packages with large
dependency trees (ffmpeg, ghc, vim). Changes:
- Discover all .so directories and include them in RPATH
- Fix ELF interpreter if it points to a conda build path
- Log patchelf errors instead of silently ignoring them
- Add conda backends for ffmpeg, ghc, and vim

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 10, 2026 14:04
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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 enhances the mise tool's ability to manage complex conda packages on Linux by improving its patchelf utility. The changes aim to resolve common issues related to shared library resolution and hardcoded build-time paths, thereby broadening the range of tools that can be successfully installed and run via the conda backend.

Highlights

  • Comprehensive RPATH Generation: Implemented a new mechanism to discover all directories containing shared libraries (.so files) within an installation and construct $ORIGIN-relative RPATHs that include all these directories, addressing issues with complex package dependency trees.
  • ELF Interpreter Fixing: Added logic to detect and correct hardcoded ELF interpreter paths in conda binaries that point to build-time locations, replacing them with local or system linkers to ensure executability.
  • Enhanced patchelf Error Logging: Modified patchelf calls to log errors at debug level instead of silently swallowing them, improving troubleshooting capabilities.
  • Expanded Conda Backend Support: Enabled conda backend support for ffmpeg, ghc, and vim by integrating the improved patchelf mechanisms, resolving previous shared library resolution failures for these tools.
Changelog
  • registry/ffmpeg.toml
    • Added conda:ffmpeg to the list of backends
    • Included a test command for ffmpeg
  • registry/ghc.toml
    • Added conda:ghc to the list of backends
  • registry/vim.toml
    • Added conda:vim to the list of backends
    • Included a test command for vim
  • src/backend/conda_linux.rs
    • Refactored fix_library_paths to use a more comprehensive RPATH generation strategy
    • Introduced find_lib_dirs to discover all shared library directories
    • Implemented build_rpath to construct $ORIGIN-relative RPATHs
    • Added fix_interpreter to handle and correct hardcoded ELF interpreter paths
    • Created is_conda_build_path to identify conda build-time paths
    • Enhanced set_rpath to log patchelf errors instead of ignoring them
Activity
  • The author has confirmed that mise run lint-fix passes.
  • The author has confirmed that mise run build compiles successfully.
  • CI mise test-tool for ffmpeg, ghc, vim is pending.
  • The pull request was generated with Claude Code.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
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

Improve Linux conda package ELF patching to better handle complex dependency trees (e.g., ffmpeg/ghc/vim) by expanding RPATH handling, fixing broken interpreters, and adding conda backends/tests in the registry.

Changes:

  • Discover all shared-library directories and generate comprehensive $ORIGIN-relative RPATHs.
  • Detect and replace conda build-time ELF interpreters with local/system linkers.
  • Add conda backends (and lightweight tests) for ffmpeg, ghc, and vim registry entries.

Reviewed changes

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

File Description
src/backend/conda_linux.rs Adds directory discovery for .so files, builds multi-entry RPATH, and patches interpreters with debug logging.
registry/vim.toml Prefers conda backend and adds a version-based smoke test.
registry/ghc.toml Prefers conda backend.
registry/ffmpeg.toml Prefers conda backend and adds a version-based smoke test.

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

Comment on lines 71 to 107
fn build_rpath(path: &Path, install_dir: &Path, lib_dirs: &[String]) -> String {
let install_str = install_dir.to_str().unwrap_or("");

// Start with the standard entries
let mut entries = Vec::new();

if path.parent().is_some_and(|p| p.ends_with("bin")) {
"$ORIGIN/../lib"
} else if path.parent().is_some_and(|p| p.ends_with("lib")) {
"$ORIGIN"
} else {
// For other locations, use absolute path
lib_dir.to_str().unwrap_or("$ORIGIN/../lib")
entries.push("$ORIGIN/../lib".to_string());
} else if let Some(parent) = path.parent() {
// For libraries, add $ORIGIN so they can find siblings
entries.push("$ORIGIN".to_string());
// Also add path to lib/ from wherever this file is
if let Ok(rel) = parent.strip_prefix(install_dir) {
let depth = rel.components().count();
if depth > 0 {
let up = "../".repeat(depth);
entries.push(format!("$ORIGIN/{}lib", up));
}
}
}

// Add all discovered lib directories as $ORIGIN-relative paths
for lib_dir in lib_dirs {
if let Some(rel) = lib_dir.strip_prefix(install_str) {
let rel = rel.trim_start_matches('/');
if let Some(parent) = path.parent() {
if let Ok(from_parent) = parent.strip_prefix(install_dir) {
let depth = from_parent.components().count();
let up = "../".repeat(depth);
let entry = format!("$ORIGIN/{}{}", up, rel);
if !entries.contains(&entry) {
entries.push(entry);
}
}
}
}
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

install_dir.to_str().unwrap_or("") can yield an empty string for non-UTF8 paths; in that case, strip_prefix("") will always succeed and may generate invalid/unsafe RPATH entries (because every string has the empty prefix). Prefer keeping lib_dirs as PathBuf and using Path::strip_prefix(install_dir) (path semantics) end-to-end; only convert to string at the final formatting step, and skip entries that cannot be represented safely.

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +172
for system_linker in &[
"/lib64/ld-linux-x86-64.so.2",
"/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2",
"/lib/ld-linux-x86-64.so.2",
"/lib/ld-linux-aarch64.so.1",
"/lib64/ld-linux-aarch64.so.1",
] {
if Path::new(system_linker).exists() {
let result = Command::new("patchelf")
.args(["--set-interpreter", system_linker, path_str])
.output();
if let Ok(o) = result {
if !o.status.success() {
debug!(
"patchelf --set-interpreter failed for {}: {}",
path_str,
String::from_utf8_lossy(&o.stderr)
);
}
}
break;
}
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The loop breaks after the first existing system linker even if patchelf --set-interpreter fails (non-zero exit). This can leave the binary unpatched even though a later candidate might work. Consider breaking only on successful patchelf execution (status success), otherwise continue trying additional linker paths; similarly, if a local linker exists but setting it fails, consider attempting the system fallback list.

Copilot uses AI. Check for mistakes.
Comment on lines 188 to 202
fn set_rpath(path: &Path, rpath: &str) {
let _ = Command::new("patchelf")
.args(["--set-rpath", rpath, path.to_str().unwrap_or("")])
let path_str = path.to_str().unwrap_or("");
let result = Command::new("patchelf")
.args(["--set-rpath", rpath, path_str])
.output();
if let Ok(o) = result {
if !o.status.success() {
debug!(
"patchelf --set-rpath failed for {}: {}",
path_str,
String::from_utf8_lossy(&o.stderr)
);
}
}
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

set_rpath logs only when patchelf runs and returns a non-zero status; if spawning/executing patchelf fails (e.g., permission/ENOENT mid-run, sandboxing), the Err case is silently ignored. Add a debug log for the Err(e) branch as well (same for the Command::new("patchelf").output() calls in fix_interpreter) to make troubleshooting consistent with the stated intent of improving error logging.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to 32
// Discover all directories containing .so files for comprehensive RPATH
let lib_dirs = find_lib_dirs(install_dir);

// Find all ELF files
let files = find_binary_files(install_dir, is_elf_file);

for file_path in files {
let rpath = determine_rpath(&file_path, &lib_dir);
set_rpath(&file_path, rpath);
for file_path in &files {
fix_interpreter(file_path, install_dir);
let rpath = build_rpath(file_path, install_dir, &lib_dirs);
set_rpath(file_path, &rpath);
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

This executes multiple patchelf subprocesses per ELF file (--print-interpreter + --set-interpreter sometimes + --set-rpath), which can become very expensive for large installs. A tangible optimization is to only call fix_interpreter for likely executables (e.g., files under bin/, or files with the executable bit set) and/or to avoid calling --print-interpreter unless needed; this keeps the RPATH work intact but reduces the number of subprocess invocations significantly.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a great pull request that significantly improves the conda backend's library path fixing on Linux, enabling support for complex packages like ffmpeg, ghc, and vim. The new logic for discovering library directories, building a comprehensive RPATH, and fixing ELF interpreters is a solid enhancement. I've added a few suggestions to further improve the robustness of the implementation, mainly concerning the handling of non-UTF8 file paths, which is a possibility on Linux systems.

Comment on lines +46 to +67
fn find_lib_dirs(install_dir: &Path) -> Vec<String> {
let mut dirs = std::collections::HashSet::new();
for entry in WalkDir::new(install_dir).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file() {
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
if name.contains(".so") {
if let Some(parent) = path.parent() {
dirs.insert(parent.to_path_buf());
}
}
}
}
}

let mut sorted: Vec<_> = dirs.into_iter().collect();
sorted.sort();
sorted
.into_iter()
.filter_map(|d| d.to_str().map(|s| s.to_string()))
.collect()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This function converts PathBuf to String and filters out non-UTF8 paths. This can be problematic on Linux where paths are not guaranteed to be UTF-8. It's more robust to work with PathBuf directly, which also simplifies the function. Note that this change will require build_rpath to be updated to accept &[PathBuf].

fn find_lib_dirs(install_dir: &Path) -> Vec<PathBuf> {
    let mut dirs = std::collections::HashSet::new();
    for entry in WalkDir::new(install_dir).into_iter().filter_map(|e| e.ok()) {
        let path = entry.path();
        if path.is_file() {
            if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
                if name.contains(".so") {
                    if let Some(parent) = path.parent() {
                        dirs.insert(parent.to_path_buf());
                    }
                }
            }
        }
    }

    let mut sorted: Vec<_> = dirs.into_iter().collect();
    sorted.sort();
    sorted
}

Comment on lines +71 to +110
fn build_rpath(path: &Path, install_dir: &Path, lib_dirs: &[String]) -> String {
let install_str = install_dir.to_str().unwrap_or("");

// Start with the standard entries
let mut entries = Vec::new();

if path.parent().is_some_and(|p| p.ends_with("bin")) {
"$ORIGIN/../lib"
} else if path.parent().is_some_and(|p| p.ends_with("lib")) {
"$ORIGIN"
} else {
// For other locations, use absolute path
lib_dir.to_str().unwrap_or("$ORIGIN/../lib")
entries.push("$ORIGIN/../lib".to_string());
} else if let Some(parent) = path.parent() {
// For libraries, add $ORIGIN so they can find siblings
entries.push("$ORIGIN".to_string());
// Also add path to lib/ from wherever this file is
if let Ok(rel) = parent.strip_prefix(install_dir) {
let depth = rel.components().count();
if depth > 0 {
let up = "../".repeat(depth);
entries.push(format!("$ORIGIN/{}lib", up));
}
}
}

// Add all discovered lib directories as $ORIGIN-relative paths
for lib_dir in lib_dirs {
if let Some(rel) = lib_dir.strip_prefix(install_str) {
let rel = rel.trim_start_matches('/');
if let Some(parent) = path.parent() {
if let Ok(from_parent) = parent.strip_prefix(install_dir) {
let depth = from_parent.components().count();
let up = "../".repeat(depth);
let entry = format!("$ORIGIN/{}{}", up, rel);
if !entries.contains(&entry) {
entries.push(entry);
}
}
}
}
}

entries.join(":")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Following the suggested change in find_lib_dirs to return Vec<PathBuf>, this function should be updated. Using install_dir.to_str().unwrap_or("") is risky and can lead to incorrect RPATHs if the path contains non-UTF8 characters. Using Path::strip_prefix is the correct and more robust way to handle this.

fn build_rpath(path: &Path, install_dir: &Path, lib_dirs: &[PathBuf]) -> String {
    let mut entries = Vec::new();

    if path.parent().is_some_and(|p| p.ends_with("bin")) {
        entries.push("$ORIGIN/../lib".to_string());
    } else if let Some(parent) = path.parent() {
        // For libraries, add $ORIGIN so they can find siblings
        entries.push("$ORIGIN".to_string());
        // Also add path to lib/ from wherever this file is
        if let Ok(rel) = parent.strip_prefix(install_dir) {
            let depth = rel.components().count();
            if depth > 0 {
                let up = "../".repeat(depth);
                entries.push(format!("$ORIGIN/{}lib", up));
            }
        }
    }

    // Add all discovered lib directories as $ORIGIN-relative paths
    for lib_dir in lib_dirs {
        if let Ok(rel_path) = lib_dir.strip_prefix(install_dir) {
            if let Some(rel) = rel_path.to_str() {
                if let Some(parent) = path.parent() {
                    if let Ok(from_parent) = parent.strip_prefix(install_dir) {
                        let depth = from_parent.components().count();
                        let up = "../".repeat(depth);
                        let entry = format!("$ORIGIN/{}{}", up, rel);
                        if !entries.contains(&entry) {
                            entries.push(entry);
                        }
                    }
                }
            }
        }
    }

    entries.join(":")
}


/// Fix ELF interpreter if it points to a conda build path
fn fix_interpreter(path: &Path, install_dir: &Path) {
let path_str = path.to_str().unwrap_or("");
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Using unwrap_or("") can hide issues with non-UTF8 paths. If path is not valid UTF-8, path_str will be an empty string, and patchelf will fail silently or operate on the wrong file. It's better to handle the None case explicitly by logging and returning early.

    let Some(path_str) = path.to_str() else {
        debug!("Skipping non-UTF8 path in fix_interpreter: {}", path.display());
        return;
    };

fn set_rpath(path: &Path, rpath: &str) {
let _ = Command::new("patchelf")
.args(["--set-rpath", rpath, path.to_str().unwrap_or("")])
let path_str = path.to_str().unwrap_or("");
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Similar to other parts of this file, unwrap_or("") can hide issues with non-UTF8 paths. If path is not valid UTF-8, path_str will be an empty string, and patchelf will fail silently. It's better to handle the None case explicitly.

    let Some(path_str) = path.to_str() else {
        debug!("Skipping non-UTF8 path in set_rpath: {}", path.display());
        return;
    };

- Use PathBuf instead of String for find_lib_dirs, use
  Path::strip_prefix for RPATH computation
- Handle non-UTF8 paths with early returns instead of unwrap_or("")
- Support aarch64 local linker (not just x86-64)
- Only break system linker loop on successful patchelf, try next
  candidate on failure
- Log Err branches (spawn failures) in all patchelf calls
- Only call fix_interpreter for files in bin/libexec (perf: skip
  shared libs that don't have PT_INTERP)
- Extract run_set_interpreter helper for cleaner control flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx force-pushed the registry/conda-complex-tools branch from d4edd42 to 239dd67 Compare February 10, 2026 14:16
@jdx
Copy link
Owner Author

jdx commented Feb 10, 2026

bugbot run

Use `name.ends_with(".so") || name.contains(".so.")` instead of
`name.contains(".so")` to avoid false positives like dbus.socket,
config.sorted, etc. that would add spurious directories to RPATH.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx force-pushed the registry/conda-complex-tools branch from bac10cc to bf3ec60 Compare February 10, 2026 14:45
@jdx
Copy link
Owner Author

jdx commented Feb 10, 2026

bugbot run

Copy link

@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 new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@jdx jdx enabled auto-merge (squash) February 10, 2026 15:08
@jdx jdx disabled auto-merge February 10, 2026 15:23
@github-actions
Copy link

github-actions bot commented Feb 10, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.9 x -- echo 21.9 ± 0.4 20.9 23.9 1.00
mise x -- echo 22.2 ± 0.6 21.1 26.5 1.02 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.9 env 21.6 ± 0.8 20.6 28.7 1.00
mise env 21.8 ± 0.6 20.6 23.8 1.01 ± 0.05

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.9 hook-env 22.0 ± 0.4 21.0 23.3 1.00
mise hook-env 22.5 ± 0.6 21.3 25.0 1.03 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.9 ls 20.0 ± 0.3 19.4 21.1 1.00
mise ls 20.5 ± 0.7 19.4 27.3 1.02 ± 0.04

xtasks/test/perf

Command mise-2026.2.9 mise Variance
install (cached) 119ms 119ms +0%
ls (cached) 73ms 73ms +0%
bin-paths (cached) 77ms 77ms +0%
task-ls (cached) 532ms 530ms +0%

jdx and others added 2 commits February 10, 2026 14:59
Conda packages use a long `_h_env_placehold` prefix that gets replaced
at install time. Since we extract packages directly (bypassing conda's
install machinery), shell script wrappers retain hardcoded build paths
and fail with exit code 127. This fixes ghc which uses shell script
wrappers in bin/ that exec paths under the build-time prefix.

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

jdx commented Feb 10, 2026

bugbot run

Sysroot directories contain compilation stub libraries (libc.so, etc.)
from the conda sysroot package. Including them in RPATH causes the
bundled libc to be loaded instead of the system one, resulting in
symbol lookup errors (e.g. __rtld_global_ro_memset_non_temporal_threshold).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jdx and others added 3 commits February 10, 2026 16:03
- Stop forward scan at quotes, newlines, spaces, colons (not just /)
  in extract_placeholder_prefix to avoid overshooting past line
  boundaries
- Exclude sysroot directories from RPATH discovery to prevent loading
  conda-bundled libc stubs instead of system libc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
vim dynamically links against libpython3.10.so and libperl.so, so
skipping these packages causes exit code 127 at runtime. Remove them
from SKIP_PACKAGES since tools that declare these as dependencies
genuinely need the shared libraries.

Also fix potential panic in extract_placeholder_prefix when the
delimiter character before a conda path is multi-byte UTF-8, and
improve test_tool diagnostics by showing captured output on exit
code 127.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix doc list item indentation warning in conda.rs SKIP_PACKAGES comment
- Remove vim from conda backend (still fails with exit 127 despite
  patchelf improvements — likely needs LD_LIBRARY_PATH or deeper fixes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
if let Err(e) = std::fs::write(path, new_text) {
debug!("failed to fix prefix in {}: {e}", path.display());
}
}
Copy link

Choose a reason for hiding this comment

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

Text prefix replacement handles only one build prefix

Medium Severity

fix_text_prefixes calls find_conda_prefix once, which returns the first _h_env_placehold-based prefix it discovers. It then replaces only that specific prefix string across all files. Since each conda dependency package is independently built with its own unique build prefix path, text files from other dependency packages (containing different prefixes) silently pass the !text.contains(&prefix) check and are never fixed. For complex packages with large dependency trees — exactly the PR's target use case — this means shell scripts, config files, or wrappers originating from dependency packages retain broken hardcoded paths.

Additional Locations (1)

Fix in Cursor Fix in Web

Conda build strings encode which dependency versions a package was
built against (e.g., py310 = Python 3.10). Use these pins to constrain
dependency resolution so we install the correct version instead of
the latest matching the loose spec (e.g., >=3.10 would pick 3.15).

This fixes vim's conda backend which needs libpython3.10 but was
getting Python 3.15 installed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx changed the title fix(backend): improve conda patchelf for complex packages fix(backend): improve conda patchelf and dependency resolution for complex packages Feb 11, 2026
Copy link

@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.

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

Bugbot Autofix is ON, but a Cloud Agent failed to start.

@@ -1,2 +1,2 @@
backends = ["asdf:mise-plugins/mise-vim"]
backends = ["conda:vim", "asdf:mise-plugins/mise-vim"]
Copy link

Choose a reason for hiding this comment

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

Known-broken conda:vim backend added as default

High Severity

The conda:vim backend is added as the first entry in vim.toml, but the PR description explicitly states under "Not included" that vim "still fails with exit code 127 despite patchelf improvements." Since backends().first() is used throughout the codebase as the default backend for tool installation (e.g., in backend_arg.rs), users installing vim will default to the known-broken conda backend. This will cause vim installations to fail or degrade to a slower fallback path.

Fix in Cursor Fix in Web

@jdx jdx merged commit aa9fdbe into main Feb 11, 2026
44 checks passed
@jdx jdx deleted the registry/conda-complex-tools branch February 11, 2026 02:18
jdx pushed a commit that referenced this pull request Feb 12, 2026
### 🚀 Features

- **(activate)** add shims directory as fallback when auto-install is
enabled by @ctaintor in [#8106](#8106)
- **(env)** add `tools` variable to tera template context by @jdx in
[#8108](#8108)
- **(set)** add --stdin flag for multiline environment variables by @jdx
in [#8110](#8110)

### 🐛 Bug Fixes

- **(backend)** improve conda patchelf and dependency resolution for
complex packages by @jdx in
[#8087](#8087)
- **(ci)** fix validate-new-tools grep pattern for test field by @jdx in
[#8100](#8100)
- **(config)** make MISE_OFFLINE work correctly by gracefully skipping
network calls by @jdx in [#8109](#8109)
- **(github)** skip v prefix for "latest" version by @jdx in
[#8105](#8105)
- **(gitlab)** resolve tool options from config for aliased tools by
@jdx in [#8084](#8084)
- **(install)** use version_expr for Flutter to fix version resolution
by @jdx in [#8081](#8081)
- **(registry)** add Linux support for tuist by @fortmarek in
[#8102](#8102)
- **(release)** write release notes to file instead of capturing stdout
by @jdx in [#8086](#8086)
- **(upgrade)** tools are not uninstalled properly due to outdated
symlink by @roele in [#8099](#8099)
- **(upgrade)** ensure uninstallation failure does not leave invalid
symlinks by @roele in [#8101](#8101)
- SLSA for in-toto statement with no signatures by @gerhard in
[#8094](#8094)
- Vfox Plugin Auto-Installation for Environment Directives by @pose in
[#8035](#8035)

### 📚 Documentation

- use mise activate for PowerShell in getting-started by @rileychh in
[#8112](#8112)

### 📦 Registry

- add conda backend for mysql by @jdx in
[#8080](#8080)
- add conda backends for 10 asdf-only tools by @jdx in
[#8083](#8083)
- added podman-tui by @tony-sol in
[#8098](#8098)

### Chore

- sort settings.toml alphabetically and add test by @jdx in
[#8111](#8111)

### New Contributors

- @ctaintor made their first contribution in
[#8106](#8106)
- @rileychh made their first contribution in
[#8112](#8112)
- @fortmarek made their first contribution in
[#8102](#8102)
- @pose made their first contribution in
[#8035](#8035)
- @gerhard made their first contribution in
[#8094](#8094)

## 📦 Aqua Registry Updates

#### New Packages (2)

- [`entireio/cli`](https://github.com/entireio/cli)
-
[`rmitchellscott/reManager`](https://github.com/rmitchellscott/reManager)

#### Updated Packages (1)

- [`atuinsh/atuin`](https://github.com/atuinsh/atuin)
jdx pushed a commit that referenced this pull request Feb 12, 2026
### 🚀 Features

- **(activate)** add shims directory as fallback when auto-install is
enabled by @ctaintor in [#8106](#8106)
- **(env)** add `tools` variable to tera template context by @jdx in
[#8108](#8108)
- **(set)** add --stdin flag for multiline environment variables by @jdx
in [#8110](#8110)

### 🐛 Bug Fixes

- **(backend)** improve conda patchelf and dependency resolution for
complex packages by @jdx in
[#8087](#8087)
- **(ci)** fix validate-new-tools grep pattern for test field by @jdx in
[#8100](#8100)
- **(config)** make MISE_OFFLINE work correctly by gracefully skipping
network calls by @jdx in [#8109](#8109)
- **(github)** skip v prefix for "latest" version by @jdx in
[#8105](#8105)
- **(gitlab)** resolve tool options from config for aliased tools by
@jdx in [#8084](#8084)
- **(install)** use version_expr for Flutter to fix version resolution
by @jdx in [#8081](#8081)
- **(registry)** add Linux support for tuist by @fortmarek in
[#8102](#8102)
- **(release)** write release notes to file instead of capturing stdout
by @jdx in [#8086](#8086)
- **(upgrade)** tools are not uninstalled properly due to outdated
symlink by @roele in [#8099](#8099)
- **(upgrade)** ensure uninstallation failure does not leave invalid
symlinks by @roele in [#8101](#8101)
- SLSA for in-toto statement with no signatures by @gerhard in
[#8094](#8094)
- Vfox Plugin Auto-Installation for Environment Directives by @pose in
[#8035](#8035)

### 📚 Documentation

- use mise activate for PowerShell in getting-started by @rileychh in
[#8112](#8112)

### 📦 Registry

- add conda backend for mysql by @jdx in
[#8080](#8080)
- add conda backends for 10 asdf-only tools by @jdx in
[#8083](#8083)
- added podman-tui by @tony-sol in
[#8098](#8098)

### Chore

- sort settings.toml alphabetically and add test by @jdx in
[#8111](#8111)

### New Contributors

- @ctaintor made their first contribution in
[#8106](#8106)
- @rileychh made their first contribution in
[#8112](#8112)
- @fortmarek made their first contribution in
[#8102](#8102)
- @pose made their first contribution in
[#8035](#8035)
- @gerhard made their first contribution in
[#8094](#8094)

## 📦 Aqua Registry Updates

#### New Packages (2)

- [`entireio/cli`](https://github.com/entireio/cli)
-
[`rmitchellscott/reManager`](https://github.com/rmitchellscott/reManager)

#### Updated Packages (1)

- [`atuinsh/atuin`](https://github.com/atuinsh/atuin)
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
…mplex packages (jdx#8087)

## Summary
- Improve Linux library path fixing (patchelf RPATH) for conda packages
with large dependency trees
- Pin conda dependency versions from build strings to install the
correct library versions
- Add conda backends for ffmpeg, ghc, and vim

## Changes to `conda_linux.rs`

### Comprehensive RPATH
Previously, RPATH was set to only `$ORIGIN/../lib` for binaries and
`$ORIGIN` for libraries. Complex packages like ffmpeg have shared
libraries spread across multiple subdirectories. Now we:
- Discover all directories containing `.so` files (using precise
`.so`/`.so.` matching)
- Build RPATH strings that include all library directories using
`$ORIGIN`-relative paths

### Interpreter fixing
If a conda binary has a hardcoded ELF interpreter pointing to a conda
build path (e.g. `/home/conda/feedstock_root/.../ld-linux-x86-64.so.2`),
the binary can't load at all (exit code 127). Now we:
- Detect conda build-time interpreter paths
- Replace with a local linker from the install dir if available, or fall
back to the system linker
- Support both x86-64 and aarch64 architectures

### Error logging
Previously, all patchelf errors were silently swallowed (`let _ = ...`).
Now they are logged at debug level for troubleshooting, including spawn
failures.

### Performance
Only call `fix_interpreter` for files in `bin/`/`libexec/`, skipping
shared libraries that don't have `PT_INTERP`.

## Changes to `conda.rs` - Build string version pinning

### Problem
Conda build strings encode which dependency versions a package was built
against (e.g., `py310` = Python 3.10, `pl5321` = Perl 5.32). However,
the dependency resolver was ignoring this and installing the latest
version matching a loose spec like `>=3.10`, which would pick Python
3.15 instead of 3.10.

### Fix
Parse build string prefixes (`py`, `pl`) to extract version pins, then
use them to constrain dependency resolution. Falls back to the original
version spec if the pinned version isn't available.

Example: vim package `vim-9.1.1858-py310pl5321h03900fa_0` now correctly
gets Python 3.10.* instead of 3.15.

## Registry updates
| Tool | conda package | Test command |
|------|--------------|-------------|
| ffmpeg | `ffmpeg` | `ffmpeg -version` |
| ghc | `ghc` | `ghc --version` |
| vim | `vim` | `vim --version` |

## Context
These tools were removed from jdx#8083 because they failed with exit code
127 (shared library resolution failure). This PR improves both the
patchelf mechanism and the dependency resolver to handle their complex
dependency trees.

## Test plan
- [x] `mise run lint` passes
- [x] `mise run build` compiles successfully
- [x] `mise x conda:vim -- vim --version` works locally (installs Python
3.10 correctly)
- [x] CI `mise test-tool` for ffmpeg — passed
- [x] CI `mise test-tool` for ghc — passed
- [ ] CI `mise test-tool` for vim — pending

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches conda install/dependency resolution and Linux binary patching
(`patchelf`), which can affect runtime behavior across many packages;
changes are targeted but broad in impact if the heuristics mis-detect
pins/prefixes or generate incorrect RPATH/interpreter settings.
> 
> **Overview**
> Improves conda-based installs for complex Linux packages by making
`patchelf` fixes more robust: builds RPATHs from *all* discovered
shared-library directories (excluding sysroots), optionally rewrites
hardcoded ELF interpreters pointing at conda build paths, and logs
`patchelf` failures instead of silently swallowing them.
> 
> Enhances conda dependency resolution to honor build-string version
pins (e.g. `py310` → Python 3.10) and stops skipping runtime
`python`/`perl`/`ruby` packages so their shared libs can be present when
tools link against them; also replaces conda placeholder prefixes in
extracted text files.
> 
> Adds `conda:` as a backend option for `ffmpeg`, `ghc`, and `vim` in
the registry (with a new `ffmpeg` smoke test), and improves `mise
test-tool` diagnostics by printing captured output for exit code `127`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8d801e0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
### 🚀 Features

- **(activate)** add shims directory as fallback when auto-install is
enabled by @ctaintor in [jdx#8106](jdx#8106)
- **(env)** add `tools` variable to tera template context by @jdx in
[jdx#8108](jdx#8108)
- **(set)** add --stdin flag for multiline environment variables by @jdx
in [jdx#8110](jdx#8110)

### 🐛 Bug Fixes

- **(backend)** improve conda patchelf and dependency resolution for
complex packages by @jdx in
[jdx#8087](jdx#8087)
- **(ci)** fix validate-new-tools grep pattern for test field by @jdx in
[jdx#8100](jdx#8100)
- **(config)** make MISE_OFFLINE work correctly by gracefully skipping
network calls by @jdx in [jdx#8109](jdx#8109)
- **(github)** skip v prefix for "latest" version by @jdx in
[jdx#8105](jdx#8105)
- **(gitlab)** resolve tool options from config for aliased tools by
@jdx in [jdx#8084](jdx#8084)
- **(install)** use version_expr for Flutter to fix version resolution
by @jdx in [jdx#8081](jdx#8081)
- **(registry)** add Linux support for tuist by @fortmarek in
[jdx#8102](jdx#8102)
- **(release)** write release notes to file instead of capturing stdout
by @jdx in [jdx#8086](jdx#8086)
- **(upgrade)** tools are not uninstalled properly due to outdated
symlink by @roele in [jdx#8099](jdx#8099)
- **(upgrade)** ensure uninstallation failure does not leave invalid
symlinks by @roele in [jdx#8101](jdx#8101)
- SLSA for in-toto statement with no signatures by @gerhard in
[jdx#8094](jdx#8094)
- Vfox Plugin Auto-Installation for Environment Directives by @pose in
[jdx#8035](jdx#8035)

### 📚 Documentation

- use mise activate for PowerShell in getting-started by @rileychh in
[jdx#8112](jdx#8112)

### 📦 Registry

- add conda backend for mysql by @jdx in
[jdx#8080](jdx#8080)
- add conda backends for 10 asdf-only tools by @jdx in
[jdx#8083](jdx#8083)
- added podman-tui by @tony-sol in
[jdx#8098](jdx#8098)

### Chore

- sort settings.toml alphabetically and add test by @jdx in
[jdx#8111](jdx#8111)

### New Contributors

- @ctaintor made their first contribution in
[jdx#8106](jdx#8106)
- @rileychh made their first contribution in
[jdx#8112](jdx#8112)
- @fortmarek made their first contribution in
[jdx#8102](jdx#8102)
- @pose made their first contribution in
[jdx#8035](jdx#8035)
- @gerhard made their first contribution in
[jdx#8094](jdx#8094)

## 📦 Aqua Registry Updates

#### New Packages (2)

- [`entireio/cli`](https://github.com/entireio/cli)
-
[`rmitchellscott/reManager`](https://github.com/rmitchellscott/reManager)

#### Updated Packages (1)

- [`atuinsh/atuin`](https://github.com/atuinsh/atuin)
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
### 🚀 Features

- **(activate)** add shims directory as fallback when auto-install is
enabled by @ctaintor in [jdx#8106](jdx#8106)
- **(env)** add `tools` variable to tera template context by @jdx in
[jdx#8108](jdx#8108)
- **(set)** add --stdin flag for multiline environment variables by @jdx
in [jdx#8110](jdx#8110)

### 🐛 Bug Fixes

- **(backend)** improve conda patchelf and dependency resolution for
complex packages by @jdx in
[jdx#8087](jdx#8087)
- **(ci)** fix validate-new-tools grep pattern for test field by @jdx in
[jdx#8100](jdx#8100)
- **(config)** make MISE_OFFLINE work correctly by gracefully skipping
network calls by @jdx in [jdx#8109](jdx#8109)
- **(github)** skip v prefix for "latest" version by @jdx in
[jdx#8105](jdx#8105)
- **(gitlab)** resolve tool options from config for aliased tools by
@jdx in [jdx#8084](jdx#8084)
- **(install)** use version_expr for Flutter to fix version resolution
by @jdx in [jdx#8081](jdx#8081)
- **(registry)** add Linux support for tuist by @fortmarek in
[jdx#8102](jdx#8102)
- **(release)** write release notes to file instead of capturing stdout
by @jdx in [jdx#8086](jdx#8086)
- **(upgrade)** tools are not uninstalled properly due to outdated
symlink by @roele in [jdx#8099](jdx#8099)
- **(upgrade)** ensure uninstallation failure does not leave invalid
symlinks by @roele in [jdx#8101](jdx#8101)
- SLSA for in-toto statement with no signatures by @gerhard in
[jdx#8094](jdx#8094)
- Vfox Plugin Auto-Installation for Environment Directives by @pose in
[jdx#8035](jdx#8035)

### 📚 Documentation

- use mise activate for PowerShell in getting-started by @rileychh in
[jdx#8112](jdx#8112)

### 📦 Registry

- add conda backend for mysql by @jdx in
[jdx#8080](jdx#8080)
- add conda backends for 10 asdf-only tools by @jdx in
[jdx#8083](jdx#8083)
- added podman-tui by @tony-sol in
[jdx#8098](jdx#8098)

### Chore

- sort settings.toml alphabetically and add test by @jdx in
[jdx#8111](jdx#8111)

### New Contributors

- @ctaintor made their first contribution in
[jdx#8106](jdx#8106)
- @rileychh made their first contribution in
[jdx#8112](jdx#8112)
- @fortmarek made their first contribution in
[jdx#8102](jdx#8102)
- @pose made their first contribution in
[jdx#8035](jdx#8035)
- @gerhard made their first contribution in
[jdx#8094](jdx#8094)

## 📦 Aqua Registry Updates

#### New Packages (2)

- [`entireio/cli`](https://github.com/entireio/cli)
-
[`rmitchellscott/reManager`](https://github.com/rmitchellscott/reManager)

#### Updated Packages (1)

- [`atuinsh/atuin`](https://github.com/atuinsh/atuin)
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