fix(cli): detect musl correctly under Bun and Alpine - #700
Conversation
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)
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
There was a problem hiding this comment.
💡 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".
| if (readdirSync("/lib").some((entry) => entry.startsWith("ld-musl-"))) { | ||
| return "musl"; |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.1present) → gnu ✅- Same image with
ldddeleted (forces the loader-scan fallback) → gnu ✅ oven/bun:1-alpine(the original issue) still → musl ✅
There was a problem hiding this comment.
1 issue found across 3 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
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)
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
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)
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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>
* 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)
* 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)
Fixes #697
Problem
On
oven/bun:1-alpinethe launcher selected the gnu binary instead of musl. Two gaps compounded (thanks @scrocquesel-ml150 for the precise report):process.report.getReport()has noheader.glibcVersionRuntimeand an emptysharedObjectsarray, so both report-based checks were inconclusive.ldd --versionfallback throws on musl — musl'slddrejects--version, printsmusl libcto stderr, and exits non-zero — and thecatchblock defaulted to"gnu".Fix
detectLibcKind()now resolves in this order:TOKSCALE_LIBC=musl|gnu|glibc— explicit env override (requested in the issue)header.glibcVersionRuntime→ gnu (unchanged)sharedObjectscontaining musl → musl (unchanged)header.release.sourceUrlnames the build flavor (e.g.bun-linux-x64-musl-baseline.zip) → musl/lib/ld-musl-<arch>.so.1(Alpine, Void-musl, …) → muslldd --version, now parsing stdout/stderr of the failed probe instead of blindly defaulting to gnuThe 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):
oven/bun:1-alpine(issue repro, same digest)oven/bun:1-alpinevia node shimnode:20-alpinenode:20-slimoven/bun:1(Debian)node:20-slim+TOKSCALE_LIBC=muslAlso:
tscbuild passes,bash -non the smoke-test script passes, and the embedded snippet still resolvescli-darwin-arm64on 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_LIBCoverride.header.release.sourceUrl, the musl loader at/lib/ld-musl-*.so.1, and by parsing stdout/stderr fromldd --version.ldd --versionprobe first; if inconclusive, scan loaders; when both loaders exist, use/etc/alpine-releaseto break ties (Alpine → musl), otherwise prefer the single present loader.TOKSCALE_LIBC=musl|gnuto force selection; mirror the logic inscripts/test-package-launchers.sh; document the override in README.Written for commit 570c851. Summary will update on new commits.