Skip to content

fix: fall back to compile-time musl detection when no system linker found#8825

Merged
jdx merged 9 commits intojdx:mainfrom
davireis:fix/compile-time-musl-fallback
Apr 2, 2026
Merged

fix: fall back to compile-time musl detection when no system linker found#8825
jdx merged 9 commits intojdx:mainfrom
davireis:fix/compile-time-musl-fallback

Conversation

@davireis
Copy link
Copy Markdown
Contributor

Summary

  • When neither glibc's ld-linux-* nor musl's ld-musl-* dynamic linker is found at runtime (e.g., scratch or busybox Docker containers), mise now falls back to the binary's compile-time target (cfg!(target_env = "musl")) instead of defaulting to glibc.
  • Fix applied to both src/platform.rs (is_musl_system()) and the mirrored logic in crates/vfox/src/config.rs (env_type()), as noted by the "keep in sync" comments.

Problem

A musl-compiled mise binary running in a minimal container (scratch + busybox, no /lib/ld-* files) incorrectly identifies the platform as glibc. This causes it to select glibc lockfile entries when both glibc and musl entries are available.

Fix

When runtime linker detection finds no /lib/ld-linux-* (glibc) and no /lib/ld-musl-* (musl), fall back to cfg!(target_env = "musl") — the binary's own compile-time target. This is a safe assumption: if the binary was compiled for musl and there's no linker to contradict that, tools installed by mise should also use musl.

Precedent

The Bun plugin (src/plugins/core/bun.rs) already uses cfg!(target_env = "musl") for its is_musl() check, so this pattern is established in the codebase.

Reproduction

  1. Build a Docker image FROM scratch with busybox and a musl-compiled mise binary
  2. Create a lockfile (.mise.lock) with both glibc and musl entries for a tool
  3. Run mise install — it picks the glibc entry (wrong)
  4. With this fix, it correctly picks the musl entry

Test plan

  • Verify cargo check passes (confirmed locally)
  • On a glibc system (e.g., Ubuntu): is_musl_system() still returns false (glibc linker detected first)
  • On a musl system (e.g., Alpine): is_musl_system() still returns true (musl linker detected)
  • On a scratch+busybox container with musl mise: is_musl_system() now returns true (compile-time fallback)
  • On a scratch+busybox container with glibc mise: is_musl_system() returns false (compile-time fallback, cfg! is false)

🤖 Generated with Claude Code

…ound

When neither glibc's ld-linux-* nor musl's ld-musl-* dynamic linker is
present (e.g., scratch or busybox Docker containers), mise defaulted to
glibc — even when the binary itself was compiled for musl. This caused
musl-compiled mise to pick glibc lockfile entries.

Fall back to cfg!(target_env = "musl") when no linker files are detected
at runtime. This mirrors the approach already used by the Bun plugin.

The fix is applied to both src/platform.rs (is_musl_system) and the
mirrored logic in crates/vfox/src/config.rs (env_type), as noted by the
"keep in sync" comments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 30, 2026

Greptile Summary

This PR fixes a bug where a musl-compiled mise binary running inside a minimal container (scratch, busybox — no /lib/ld-* files) would incorrectly identify itself as running on a glibc system, causing it to select glibc lockfile entries instead of musl ones. The fix adds a compile-time fallback (cfg!(target_env = "musl")) used only when neither a glibc nor a musl dynamic linker is found at runtime. A new MISE_LIBC env var override is also introduced and documented.

Key changes:

  • src/platform.rs and crates/vfox/src/config.rs: Detection chain now falls back to the binary's compile-time target_env when no linker is found. Both "keep in sync" implementations are updated consistently.
  • MISE_LIBC env var override: Users can explicitly force detection to musl or gnu; unknown values are ignored and fall through to runtime detection.
  • /lib64 musl check: The musl linker search is extended to also scan /lib64 — this mirrors the glibc check but is dead code in practice since musl distros only use /lib.
  • Docs: New section in docs/mise-cookbook/docker.md documents the MISE_LIBC override.
  • Tests: New tests cover the Linux-always-returns-Some property and the musl-binary-returns-musl property; the musl-gated tests have a known CI concern discussed in prior review threads.

Confidence Score: 4/5

Core runtime logic is correct and safe to ship; minor cleanup and known test-robustness concerns remain before merge.

The fix itself is correct — the compile-time fallback is sound, MISE_LIBC validation is properly restricted to gnu/musl, and the two mirrored implementations stay in sync. Score is 4 rather than 5 because (a) the musl-gated tests can produce false failures when run on a glibc CI host, an unresolved concern noted in prior review threads, and (b) the /lib64 musl check is unnecessary dead code introduced alongside the real fix.

src/platform.rs and crates/vfox/src/config.rs — the musl-gated tests and the /lib64-for-musl filesystem scan.

Important Files Changed

Filename Overview
src/platform.rs Adds MISE_LIBC override, /lib64 musl check (unnecessary dead code), and compile-time fallback; core fix is correct but musl-gated tests can fail on glibc CI runners
crates/vfox/src/config.rs Mirrors platform.rs changes; MISE_LIBC validation now correct; same /lib64-for-musl over-generalisation and test robustness concerns as platform.rs
docs/mise-cookbook/docker.md Adds clear documentation for the new MISE_LIBC override; accurate description of valid values and fallback behaviour

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[is_musl_system / env_type called] --> B{MISE_LIBC set?}
    B -- "musl" --> R_MUSL[return musl]
    B -- "gnu" --> R_GNU[return gnu/false]
    B -- invalid / unset --> C{"/lib or /lib64\nhas ld-linux-*?"}
    C -- yes --> R_GNU
    C -- no --> D{"/lib or /lib64\nhas ld-musl-*?"}
    D -- yes --> R_MUSL
    D -- no --> E{"cfg!(target_env = musl)?"}
    E -- true --> R_MUSL
    E -- false --> F{"config.rs only:\ncfg!(target_env = gnu)?"}
    F -- true --> R_GNU
    F -- false --> R_NONE[return None]
Loading

Reviews (8): Last reviewed commit: "[autofix.ci] apply automated fixes" | Re-trigger Greptile

Copy link
Copy Markdown
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 pull request introduces a fallback mechanism to detect the environment type (musl) using compile-time configuration when no dynamic linker is found in the filesystem, which is useful for minimal environments like scratch or busybox containers. The review feedback suggests improving the robustness of this detection by checking both /lib and /lib64 directories for the musl linker and adding a similar fallback for GNU environments.

Comment thread crates/vfox/src/config.rs Outdated
Comment on lines +61 to +68
if has_file_prefix("/lib", "ld-musl-") {
return Some("musl".to_string());
}
// No linker found at all (e.g., scratch/busybox container) —
// fall back to the binary's compile-time target
if cfg!(target_env = "musl") {
return Some("musl".to_string());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For consistency with the glibc detection logic above, consider checking both /lib and /lib64 for the musl linker. Additionally, to fully implement the "fallback to compile-time target" logic, you could also check for cfg!(target_env = "gnu") to ensure env_type() returns Some("gnu") even in scratch containers when using a glibc-compiled binary.

        for dir in ["/lib", "/lib64"] {
            if has_file_prefix(dir, "ld-musl-") {
                return Some("musl".to_string());
            }
        }
        // No linker found at all (e.g., scratch/busybox container) —
        // fall back to the binary's compile-time target
        if cfg!(target_env = "musl") {
            return Some("musl".to_string());
        }
        if cfg!(target_env = "gnu") {
            return Some("gnu".to_string());
        }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in dd4ce3d — now checks both /lib and /lib64 for musl, and added cfg!(target_env = "gnu") fallback.

Comment thread src/platform.rs Outdated
Comment on lines +203 to +208
if has_file_prefix("/lib", "ld-musl-") {
return true;
}
// No linker found at all (e.g., scratch/busybox container) —
// fall back to the binary's compile-time target
cfg!(target_env = "musl")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For consistency with the glibc detection logic above, consider checking both /lib and /lib64 for the musl linker. This ensures that the runtime detection is robust across different Linux distributions before falling back to the compile-time target.

        for dir in ["/lib", "/lib64"] {
            if has_file_prefix(dir, "ld-musl-") {
                return true;
            }
        }
        // No linker found at all (e.g., scratch/busybox container) —
        // fall back to the binary's compile-time target
        cfg!(target_env = "musl")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in dd4ce3d — now checks both /lib and /lib64 for musl before falling back to compile-time target.

bonitao and others added 3 commits March 30, 2026 18:53
- Check both /lib and /lib64 for musl linker (consistent with glibc detection)
- Add cfg!(target_env = "gnu") fallback in env_type() for completeness

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- test_musl_binary_detects_musl: verifies musl binary always returns true
- test_current_platform_has_qualifier: ensures qualifier is never None on Linux
- test_env_type_returns_some_on_linux: env_type() never returns None
- test_env_type_musl_binary_returns_musl: musl binary reports musl

All tests are #[cfg(target_os = "linux")] — will run in CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Platform::current() only sets qualifier to Some("musl") when
is_musl_system() is true. On glibc it's None. The test was
incorrectly asserting is_some() on all Linux, but should only
check musl-compiled binaries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/platform.rs
Comment thread crates/vfox/src/config.rs
Allows users to force musl or glibc detection via MISE_LIBC=musl
or MISE_LIBC=gnu. Useful for scratch containers, cross-compilation,
and testing. Checked before runtime linker detection.

Also fix test that failed on glibc CI (qualifier is only set for musl).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread crates/vfox/src/config.rs Outdated
Invalid values are now silently ignored (fall through to runtime
detection) instead of being passed through. This keeps is_musl_system()
and env_type() in sync — both reject invalid input identically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@jdx jdx left a comment

Choose a reason for hiding this comment

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

A few things to address before merging:

  1. Fix MISE_LIBC validation in config.rsplatform.rs correctly only accepts "musl" via eq_ignore_ascii_case, but config.rs blindly passes through any value (Some(val.to_lowercase())). MISE_LIBC=typo would silently produce Some("typo") and cause wrong tool selection. Validate it the same way as platform.rs.

  2. Remove the tautology teststest_mise_libc_env_var_parsing and test_mise_libc_env_var_accepted test Rust's eq_ignore_ascii_case and to_lowercase, not your actual code. The Lazy/LazyLock caching means you can't test the env var override in-process anyway, so these tests just add noise. Remove them.

  3. Document MISE_LIBC — This is a useful escape hatch but it's not discoverable anywhere. Add it to the docs/settings so users can find it.

This comment was generated by Claude Code.

autofix-ci Bot and others added 3 commits March 31, 2026 11:46
- Remove test_mise_libc_env_var_validation and test_mise_libc_env_var_accepted
  (tested Rust's string methods, not our code; LazyLock caching prevents
  in-process env var testing anyway)
- Add MISE_LIBC documentation to docs/mise-cookbook/docker.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@davireis
Copy link
Copy Markdown
Contributor Author

A few things to address before merging:

  1. Fix MISE_LIBC validation in config.rsplatform.rs correctly only accepts "musl" via eq_ignore_ascii_case, but config.rs blindly passes through any value (Some(val.to_lowercase())). MISE_LIBC=typo would silently produce Some("typo") and cause wrong tool selection. Validate it the same way as platform.rs.
  2. Remove the tautology teststest_mise_libc_env_var_parsing and test_mise_libc_env_var_accepted test Rust's eq_ignore_ascii_case and to_lowercase, not your actual code. The Lazy/LazyLock caching means you can't test the env var override in-process anyway, so these tests just add noise. Remove them.
  3. Document MISE_LIBC — This is a useful escape hatch but it's not discoverable anywhere. Add it to the docs/settings so users can find it.

This comment was generated by Claude Code.

All addressed.

@jdx jdx enabled auto-merge (squash) April 2, 2026 03:54
@jdx jdx disabled auto-merge April 2, 2026 03:54
@jdx jdx merged commit ac3aa3a into jdx:main Apr 2, 2026
36 checks passed
mise-en-dev added a commit that referenced this pull request Apr 2, 2026
### 🚀 Features

- **(install)** add per-tool install_before option by @sargunv-headway
in [#8842](#8842)

### 🐛 Bug Fixes

- **(cli)** respect `-q` flag in `mise prepare` command by @Marukome0743
in [#8792](#8792)
- fall back to compile-time musl detection when no system linker found
by @davireis in [#8825](#8825)

### 📚 Documentation

- fix GitHub capitalization in Alpine docs by @Rohan5commit in
[#8844](#8844)

### 📦 Registry

- add dbt-fusion
([aqua:getdbt.com/dbt-fusion](https://github.com/getdbt.com/dbt-fusion))
by @ryan-pip in [#8837](#8837)

### New Contributors

- @Marukome0743 made their first contribution in
[#8792](#8792)
- @sargunv-headway made their first contribution in
[#8842](#8842)
- @Rohan5commit made their first contribution in
[#8844](#8844)
- @ryan-pip made their first contribution in
[#8837](#8837)
- @rndmh3ro made their first contribution in
[#8839](#8839)

## 📦 Aqua Registry Updates

#### New Packages (1)

- [`azu/dockerfile-pin`](https://github.com/azu/dockerfile-pin)

#### Updated Packages (4)

- [`anthropics/claude-code`](https://github.com/anthropics/claude-code)
- [`dandavison/delta`](https://github.com/dandavison/delta)
- [`goreleaser/goreleaser`](https://github.com/goreleaser/goreleaser)
- [`zellij-org/zellij`](https://github.com/zellij-org/zellij)
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