Repository navigation
toolchain install llvm: payload patchelf'd against xim-x-glibc but the runtime dep is not installed — clang exec 127 on bare runners (gcc path provides it) #259
Description
Activity
Root-cause investigation: the "missing dep declaration" hypothesis is wrong — the real chain is elsewhere
Traced this against the mcpp tree at
af25d18(0.0.101), the local xim-pkgindex checkouts, and the xlings sources. Summary: the symptom is exactly as reported, but the attribution in the body ("missing runtime dependency in the llvm xim package") does not hold. Three separate findings, one of which is a previously-unknown mcpp bug.1. llvm.lua does declare glibc — hypothesis retracted
xim-pkgindex pkgs/l/llvm.lua:25-31:xpm = { linux = { deps = { "xim:glibc@2.39", "xim:linux-headers@5.11.1", "xim:zlib@1.3.1", "xim:libxml2@2.13.5" },
Present since
8e432d99("feat(llvm): add Linux x86_64 support with split packages (#182)"), identical in all three local checkouts, and structurally the same aspkgs/g/gcc.lua:34-35.xlings info llvmconfirms it resolves:runtime deps xim:glibc@2.39 xim:linux-headers@5.11.1 xim:zlib@1.3.1 xim:libxml2@2.13.5.llvm.lua is also deliberately fail-loud when glibc is absent (
:234-239,log.error("glibc payload not found …"); return false). And the PT_INTERP/RUNPATH rewrite is not done by llvm.lua at all — it is xlings' declarative elfpatch, driven by the declared dep nodes (glibc declaresexports.runtime.loader).So "gcc declares it, llvm doesn't" is not the difference. Both declare it.
2. mcpp does not bypass xlings' dep resolution either
Checked because the natural follow-up hypothesis is "mcpp isn't running the full install chain":
XLINGS_HOMEis always$MCPP_HOME/registry(src/config.cppm:82-84; every subprocess launch atsrc/xlings.cppm:734,743,752,1029,1186). The two homes do not diverge.- All three install paths —
install_direct(xlings.cppm:1093-1105),install_with_progress(:955-1015),install→interface install_packages(:1029) — funnel into xlings'xim::cmd_install, which callsresolve(catalog, …)unconditionally. (noDepsis accepted by the interface schema and forwarded, but never read.) - Disk evidence on a working machine: in
~/.mcpp/registry/data/xpkgs, the dirs without a.mcpp_okmarker are exactly the transitive deps —xim-x-glibc/2.39,xim-x-linux-headers/5.11.1,xim-x-zlib/1.3.1,xim-x-libxml2/2.13.5,xim-x-binutils/2.42, ….mcpp_okis written only by mcpp for packages it requested directly, so those dep payloads demonstrably arrived via xlings' own resolution.
3. But mcpp's own sysroot safety-net is dead code — new bug
This is the part that was not on anyone's radar. Two sites:
// src/toolchain/lifecycle.cppm:562-567 and src/build/prepare.cppm:1074-1076 for (auto dep : {"xim:glibc", "xim:linux-headers"}) fetcher.resolve_xpkg_path(dep, /*autoInstall=*/true, &progress);
resolve_xpkg_pathrequires<name>@<version>:parse_targetleavesversionempty when there is no@(src/pm/package_fetcher.cppm:770-790), and:805-810returnsunexpected("invalid xpkg target 'xim:glibc': expected `<name>@<version>`")immediately. These loops have never installed anything. And the failure was never visible, because one call site discards the result with
(void)(prepare.cppm:1075) and the other only logs at debug level (lifecycle.cppm:566). No pinned glibc/linux-headers version constants exist (src/xlings.cppm:33-38has only patchelf/ninja/xlings/nasm), and no test covers either loop.So the belt-and-braces mcpp advertises for the sysroot payloads is a no-op on every platform, every version.
4. Most probable root cause: a silent per-dep skip on the xlings side
xlings src/core/xim/installer.cppm:906-950— whenload_packageorresolve_download_resource_fails for a dep node, it islog::warn+continue, so the node never entersplannedDownloads. Phase 2's guard (:1101) only errors for nodes that were planned, so noInstallPhase::Failedis emitted,failedCountstays 0, andcmd_installreturns 0 (commands.cppm:527). The arch gate at:1063-1076has the same shape.Net effect: llvm installs, elfpatch bakes the declared glibc interpreter path into
bin/clang*, glibc was silently dropped, exit code 0 — precisely the reported symptom, and it explains why it only shows up on bare runners (anywhere glibc is already parked, the skip is harmless).Filing this separately on the xlings side; it does not block the mcpp-side work.
5. Two more mcpp-side amplifiers worth recording
post_installrecords a degraded state as success.src/toolchain/post_install.cppm:364-390: whenfind_sandbox_glibc_lib()returns empty the whole patchelf branch is skipped with no warning at all (the gcc path at least warns,:358-361);fixup_clang_cfgthen rewrites everybin/*.cfgwith the glibc-B/-L/--dynamic-linker/-isystemlines omitted — overwriting the cfg llvm.lua would have refused to write — and the idempotency marker (.mcpp-fixup.json,kFixupRev) recordsglibcLib: ""as a successful fixup, so a reinstall will not retry.- No post-install liveness check.
lifecycle.cppm:578-585only verifies the frontend file exists; nothing runsclang++ --version.mcpp doctor(src/doctor.cppm:239-313)readelf -ds installed toolchains and warns on missing RUNPATH entries, but never inspects PT_INTERP — which is the fatal axis here. Its warning text ("its providing xim package may have been removed") is also the wrong hint when the package was never installed in the first place.
What this changes for the issue
The title/body attribution ("missing runtime dependency in the llvm xim package") should be read as retracted. The accurate framing is: the install chain can drop a declared dependency silently and still report success, and mcpp then has no gate that would notice the resulting toolchain cannot exec.
The
mcpp toolchain install gccworkaround remains valid for now (it parks glibc by a different route). Planned mcpp-side work — pinned versions on the sysroot safety-net with the result actually checked, a post-install--versionsmoke probe, PT_INTERP verification indoctor, and making thepost_installskip loud instead of silent — is captured in.agents/docs/2026-07-22-v0.0.102-batch-254-261-design.md§6.- added a commit that references this issue
on Jul 21, 2026 Fixed by #660, released as 2026.9.17.2 (commit
9bc00f8).This issue's mechanism was that the llvm payload is patched against
xim-x-glibc, but nothing installed that payload — the gcc path only appeared to work because xlings installs glibc as a dependency of the gcc toolchain, so on a bare runnerclangexec'd 127 andgccdid not.The half that was never visible: the two loops that were meant to install it passed
xim:glibcwithout a version, whichFetcher::resolve_xpkg_pathrejects outright, and the rejection was logged at debug level and discarded. So the declared runtime was never installed by mcpp on any path; it was present only as a side effect of a toolchain install. A toolchain restored from a CI cache is not reinstalled, so a cache holding another glibc revision left the declared payload absent — and a directory scan that accepted any2.44.xfor2.44concealed even that until two revisions were present at once.The fix:
mcpp::toolchain::ensure_declared_runtime(src/toolchain/lifecycle.cppm:868) installsxim:glibc@<declared>through xlings and re-resolves the binding. It runs before the first fixup that consumes a C runtime — gcc and llvm payloads alike (src/build/prepare.cppm:2530,lifecycle.cppm:1078), on Linux, for the glibc provider, and only when the binding locates no payload.select_glibc_payload_libandprobelook the exact version up only;payload_dir_for_versionandneeds_linux_sysroot_payloadsare gone. A missing payload is now reported with the coordinate that provides it, instead of a 127.- The xlings released beside the mcpp executable became an acquisition source ahead of
PATH, which is what makes this work on a machine whosePATHxlings is older than the pin.
Criteria: unit tests
GlibcPayload.*/DeclaredRuntime.*, andtests/e2e/737_the_declared_runtime_payload_is_installed.sh(cache-shaped home, offline and online legs) plus687leg C. Both e2e are red on 2026.9.17.1 and green on 2026.9.17.2, so they measure the change rather than the state.Closing.
Summary
mcpp toolchain install llvm 22.1.8on a bare machine produces binaries that cannot exec: the xim llvm payload is patchelf'd with an ELF interpreter (and RUNPATH) pointing into…/registry/data/xpkgs/xim-x-glibc/2.39/…, but the install does not pull thexim-x-glibcruntime package. First use then fails with exit 127 (ENOENT on the interpreter):Seen on GitHub
ubuntu-latest(opencv-m CI, mcpp 0.0.101 release tarball flow — run 29822673295, job linux-llvm-22). Local machines never notice because something else (e.g. an earliermcpp toolchain install gcc) already parked the glibc runtime.Evidence (local, same payloads):
xim-x-gccbinaries have the same interpreter, and the gcc install path evidently DOES provide the runtime (the long-standing gcc CI legs work on bare runners) — so this looks like a missing runtime dependency in the llvm xim package (glibc, possibly zlib/libxml2 from the RUNPATH too), not an engine problem.Repro
Workaround
Install gcc first (
mcpp toolchain install gcc 16.1.0) — it parks xim-x-glibc and the llvm binaries start working. opencv-m CI does this now (comment links here).Environment: mcpp 0.0.101 release tarball, ubuntu-latest (24.04).