Skip to content

fix(cli): detect musl correctly under Bun and Alpine - #700

Merged
junhoyeo merged 3 commits into
mainfrom
fix/libc-musl-detection
Jun 10, 2026
Merged

fix(cli): detect musl correctly under Bun and Alpine#700
junhoyeo merged 3 commits into
mainfrom
fix/libc-musl-detection

Conversation

@junhoyeo

@junhoyeo junhoyeo commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Fixes #697

Problem

On oven/bun:1-alpine the launcher selected the gnu binary instead of musl. Two gaps compounded (thanks @scrocquesel-ml150 for the precise report):

  1. Bun's process.report.getReport() has no header.glibcVersionRuntime and an empty sharedObjects array, so both report-based checks were inconclusive.
  2. The ldd --version fallback throws on musl — musl's ldd rejects --version, prints musl libc to stderr, and exits non-zero — and the catch block defaulted to "gnu".

Fix

detectLibcKind() now resolves in this order:

  1. TOKSCALE_LIBC=musl|gnu|glibc — explicit env override (requested in the issue)
  2. header.glibcVersionRuntime → gnu (unchanged)
  3. sharedObjects containing musl → musl (unchanged)
  4. Bun's header.release.sourceUrl names the build flavor (e.g. bun-linux-x64-musl-baseline.zip) → musl
  5. musl dynamic loader present at /lib/ld-musl-<arch>.so.1 (Alpine, Void-musl, …) → musl
  6. ldd --version, now parsing stdout/stderr of the failed probe instead of blindly defaulting to gnu

The same logic is mirrored in scripts/test-package-launchers.sh, and the override is documented in the README's Supported Platforms section.

Verification

Ran the detection function across Docker images (including the exact digest from the issue):

Image Runtime Before After
oven/bun:1-alpine (issue repro, same digest) bun gnu ❌ musl
oven/bun:1-alpine via node shim bun gnu ❌ musl
node:20-alpine node musl musl ✅
node:20-slim node gnu gnu ✅
oven/bun:1 (Debian) bun gnu gnu ✅
node:20-slim + TOKSCALE_LIBC=musl node n/a musl

Also: tsc build passes, bash -n on the smoke-test script passes, and the embedded snippet still resolves cli-darwin-arm64 on macOS.


Summary by cubic

Fix libc detection in the CLI launcher so musl is selected on Bun/Alpine and glibc isn’t misdetected on hosts with musl tools. Adds a TOKSCALE_LIBC override.

  • Bug Fixes
    • Detect musl via Bun’s header.release.sourceUrl, the musl loader at /lib/ld-musl-*.so.1, and by parsing stdout/stderr from ldd --version.
    • Run the ldd --version probe first; if inconclusive, scan loaders; when both loaders exist, use /etc/alpine-release to break ties (Alpine → musl), otherwise prefer the single present loader.
    • Add TOKSCALE_LIBC=musl|gnu to force selection; mirror the logic in scripts/test-package-launchers.sh; document the override in README.

Written for commit 570c851. Summary will update on new commits.

Review in cubic

Bun's process.report has no glibcVersionRuntime and an empty
sharedObjects list, and musl's ldd rejects --version (it prints "musl
libc" to stderr and exits non-zero), so the launcher fell through to
the glibc default on Alpine and selected the gnu binary.

Detection now also checks Bun's release.sourceUrl build flavor, the
musl dynamic loader at /lib/ld-musl-*.so.1, and parses stdout/stderr
captured from the failed ldd probe. A new TOKSCALE_LIBC=musl|gnu env
var forces the result when detection cannot.

Fixes #697

Constraint: launcher must stay dependency-free and work under both Node and Bun
Rejected: parsing ldd stderr alone | Bun never reaches ldd when spawn shims differ; loader-file check is sturdier and runs first
Confidence: high
Scope-risk: narrow
Not-tested: arm64 musl images (logic is arch-independent; verified x64 musl/glibc via Docker)
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
tokscale Ignored Ignored Preview Jun 10, 2026 12:00am

Request Review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d35bc4be5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/cli/src/index.ts Outdated
Comment on lines +61 to +62
if (readdirSync("/lib").some((entry) => entry.startsWith("ld-musl-"))) {
return "musl";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not treat any musl loader as the host libc

When the launcher runs under Bun on a glibc system that also has musl installed (for example Debian/Ubuntu with musl or musl-tools), /lib/ld-musl-*.so.1 can exist even though the host libc is still glibc. Because Bun does not provide glibcVersionRuntime, this new check returns musl before the ldd probe can identify glibc, so the launcher selects @tokscale/cli-linux-*-musl; with npm/pnpm installs the musl optional package is skipped on glibc while the gnu package is present, causing the launcher to fail to find the installed binary.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch — fixed in 0a9fcbb. The ldd probe now runs before any filesystem check and short-circuits on explicit musl/glibc/gnu mentions in its output (including stderr from the failed musl probe). The loader scan is now a last resort for when ldd is missing or inconclusive, and it prefers the glibc loader (ld-linux-* in /lib or /lib64) when both are present, since a musl loader can coexist on glibc hosts but not the reverse.

Verified in Docker:

  • oven/bun:1 + apt-get install musl (your exact scenario: Bun, glibc host, /lib/ld-musl-*.so.1 present) → gnu
  • Same image with ldd deleted (forces the loader-scan fallback) → gnu
  • oven/bun:1-alpine (the original issue) still → musl

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/cli/src/index.ts Outdated
The loader-file check ran before the ldd probe, so a glibc host with
the musl package installed (e.g. Debian with musl-tools) was detected
as musl under Bun, where process.report is inconclusive. The ldd probe
now runs first and short-circuits on explicit musl/glibc/gnu mentions;
the loader scan is a last resort that prefers the glibc loader when
both are present, since musl can coexist on glibc hosts but not the
reverse.

Constraint: Bun's process.report cannot distinguish the host libc on glibc systems
Rejected: keeping loader check first and excluding known-coexistence paths | any allowlist of paths is fragile across distros; probe order fixes the class of problem
Confidence: high
Scope-risk: narrow
Not-tested: glibc hosts where ldd --version mentions neither glibc nor gnu (falls through to loader scan, which prefers ld-linux)

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 2 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/cli/src/index.ts Outdated
Preferring the glibc loader unconditionally mis-detects Alpine with
gcompat installed (it ships an ld-linux-* stub) as gnu in the rare
case the loader scan is reached. A lone loader still wins outright;
when both loaders exist, /etc/alpine-release decides, which resolves
both coexistence directions (Debian+musl package -> gnu, Alpine+gcompat
-> musl).

Constraint: the loader scan only runs when process.report and ldd are both inconclusive
Rejected: parsing /etc/os-release for musl-based distro IDs | more moving parts for the same rare branch; alpine-release covers the dominant musl distro
Confidence: high
Scope-risk: narrow
Not-tested: non-Alpine musl distros with glibc compat stubs and no ldd (still resolve gnu; TOKSCALE_LIBC=musl covers them)

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/test-package-launchers.sh">

<violation number="1" location="scripts/test-package-launchers.sh:98">
P2: Tie-break logic misclassifies non-Alpine musl systems as gnu when both loaders exist.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

const hasMuslLoader = loaderPresent("ld-musl-");
if (hasGnuLoader !== hasMuslLoader) return hasMuslLoader ? "musl" : "gnu";
if (hasGnuLoader && hasMuslLoader) {
return existsSync("/etc/alpine-release") ? "musl" : "gnu";

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.

P2: Tie-break logic misclassifies non-Alpine musl systems as gnu when both loaders exist.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/test-package-launchers.sh, line 98:

<comment>Tie-break logic misclassifies non-Alpine musl systems as gnu when both loaders exist.</comment>

<file context>
@@ -90,8 +91,12 @@ function detectLibcKind() {
+  const hasMuslLoader = loaderPresent("ld-musl-");
+  if (hasGnuLoader !== hasMuslLoader) return hasMuslLoader ? "musl" : "gnu";
+  if (hasGnuLoader && hasMuslLoader) {
+    return existsSync("/etc/alpine-release") ? "musl" : "gnu";
+  }
 
</file context>

@junhoyeo
junhoyeo merged commit 19dc2ac into main Jun 10, 2026
8 checks passed
@junhoyeo
junhoyeo deleted the fix/libc-musl-detection branch June 10, 2026 00:04
pinion05 added a commit to pinion05/tokscale that referenced this pull request Jun 23, 2026
* fix(cli): detect musl correctly under Bun and Alpine

Bun's process.report has no glibcVersionRuntime and an empty
sharedObjects list, and musl's ldd rejects --version (it prints "musl
libc" to stderr and exits non-zero), so the launcher fell through to
the glibc default on Alpine and selected the gnu binary.

Detection now also checks Bun's release.sourceUrl build flavor, the
musl dynamic loader at /lib/ld-musl-*.so.1, and parses stdout/stderr
captured from the failed ldd probe. A new TOKSCALE_LIBC=musl|gnu env
var forces the result when detection cannot.

Fixes junhoyeo#697

Constraint: launcher must stay dependency-free and work under both Node and Bun
Rejected: parsing ldd stderr alone | Bun never reaches ldd when spawn shims differ; loader-file check is sturdier and runs first
Confidence: high
Scope-risk: narrow
Not-tested: arm64 musl images (logic is arch-independent; verified x64 musl/glibc via Docker)

* fix(cli): don't treat a coexisting musl loader as the host libc

The loader-file check ran before the ldd probe, so a glibc host with
the musl package installed (e.g. Debian with musl-tools) was detected
as musl under Bun, where process.report is inconclusive. The ldd probe
now runs first and short-circuits on explicit musl/glibc/gnu mentions;
the loader scan is a last resort that prefers the glibc loader when
both are present, since musl can coexist on glibc hosts but not the
reverse.

Constraint: Bun's process.report cannot distinguish the host libc on glibc systems
Rejected: keeping loader check first and excluding known-coexistence paths | any allowlist of paths is fragile across distros; probe order fixes the class of problem
Confidence: high
Scope-risk: narrow
Not-tested: glibc hosts where ldd --version mentions neither glibc nor gnu (falls through to loader scan, which prefers ld-linux)

* fix(cli): break loader-coexistence ties with the distro marker

Preferring the glibc loader unconditionally mis-detects Alpine with
gcompat installed (it ships an ld-linux-* stub) as gnu in the rare
case the loader scan is reached. A lone loader still wins outright;
when both loaders exist, /etc/alpine-release decides, which resolves
both coexistence directions (Debian+musl package -> gnu, Alpine+gcompat
-> musl).

Constraint: the loader scan only runs when process.report and ldd are both inconclusive
Rejected: parsing /etc/os-release for musl-based distro IDs | more moving parts for the same rare branch; alpine-release covers the dominant musl distro
Confidence: high
Scope-risk: narrow
Not-tested: non-Alpine musl distros with glibc compat stubs and no ldd (still resolve gnu; TOKSCALE_LIBC=musl covers them)
t1000040 pushed a commit to tmobi-internal/tokscale that referenced this pull request Jun 30, 2026
* fix(cli): detect musl correctly under Bun and Alpine

Bun's process.report has no glibcVersionRuntime and an empty
sharedObjects list, and musl's ldd rejects --version (it prints "musl
libc" to stderr and exits non-zero), so the launcher fell through to
the glibc default on Alpine and selected the gnu binary.

Detection now also checks Bun's release.sourceUrl build flavor, the
musl dynamic loader at /lib/ld-musl-*.so.1, and parses stdout/stderr
captured from the failed ldd probe. A new TOKSCALE_LIBC=musl|gnu env
var forces the result when detection cannot.

Fixes junhoyeo#697

Constraint: launcher must stay dependency-free and work under both Node and Bun
Rejected: parsing ldd stderr alone | Bun never reaches ldd when spawn shims differ; loader-file check is sturdier and runs first
Confidence: high
Scope-risk: narrow
Not-tested: arm64 musl images (logic is arch-independent; verified x64 musl/glibc via Docker)

* fix(cli): don't treat a coexisting musl loader as the host libc

The loader-file check ran before the ldd probe, so a glibc host with
the musl package installed (e.g. Debian with musl-tools) was detected
as musl under Bun, where process.report is inconclusive. The ldd probe
now runs first and short-circuits on explicit musl/glibc/gnu mentions;
the loader scan is a last resort that prefers the glibc loader when
both are present, since musl can coexist on glibc hosts but not the
reverse.

Constraint: Bun's process.report cannot distinguish the host libc on glibc systems
Rejected: keeping loader check first and excluding known-coexistence paths | any allowlist of paths is fragile across distros; probe order fixes the class of problem
Confidence: high
Scope-risk: narrow
Not-tested: glibc hosts where ldd --version mentions neither glibc nor gnu (falls through to loader scan, which prefers ld-linux)

* fix(cli): break loader-coexistence ties with the distro marker

Preferring the glibc loader unconditionally mis-detects Alpine with
gcompat installed (it ships an ld-linux-* stub) as gnu in the rare
case the loader scan is reached. A lone loader still wins outright;
when both loaders exist, /etc/alpine-release decides, which resolves
both coexistence directions (Debian+musl package -> gnu, Alpine+gcompat
-> musl).

Constraint: the loader scan only runs when process.report and ldd are both inconclusive
Rejected: parsing /etc/os-release for musl-based distro IDs | more moving parts for the same rare branch; alpine-release covers the dominant musl distro
Confidence: high
Scope-risk: narrow
Not-tested: non-Alpine musl distros with glibc compat stubs and no ldd (still resolve gnu; TOKSCALE_LIBC=musl covers them)
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.

Unable to detect musl lib

1 participant