docs: the standard Linux binary needs glibc 2.35+, not 2.38+

Built in the pinned ubuntu:22.04 container, the standard Linux binary
requires at most glibc 2.35 and GLIBCXX_3.4.30. Update the comments
that said 2.38+: install.sh, the npm and PyPI wrappers, cli.c, the
Homebrew formula, smoke-test.sh, _smoke.yml, check-glibc-compat.sh and
the local glibc-floor venue. The installers keep choosing the -portable
archive, since a 2.35 floor still excludes Debian 11, RHEL 8 and Ubuntu
20.04.

The local smoke-glibc-floor lane keeps its assertion and now says what
it proves: a binary built in the noble test image refusing to start
there shows the venue is older than the build image.

Refs #2486

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-11-02 04:02:21 +01:00
1 parent 9e6eeddbac
commit 0fc2d9923b
10 files changed
+47 -36

No files matched your search

+9 -8
View File
@@ -41,12 +41,13 @@ jobs:
# Broad legs are REQUIRED gates (no optional / continue-on-error):
# every smoke leg must pass. No `optional` flags anywhere.
#
# NOTE: the *dynamic* linux binary links glibc 2.38+ and cannot run on
# older distros by design — older-glibc coverage is the -portable (static)
# binary's job, exercised green by the smoke-linux-portable broad legs
# (ubuntu-22.04 / 22.04-arm). So the dynamic broad legs stay on
# forward-compatible OSes only (macOS); running the dynamic binary on
# ubuntu-22.04 would fail Phase 1 (glibc too old), not a real regression.
# NOTE: the *dynamic* linux binary is built in a pinned Ubuntu 22.04
# container (_build.yml build-linux) and links glibc 2.35+; the
# composition gate (A6) asserts that floor on the built bytes, so no
# broad leg re-runs it on a 22.04 runner. Older-glibc coverage stays
# the -portable (static) binary's job, exercised green by the
# smoke-linux-portable broad legs (ubuntu-22.04 / 22.04-arm) and the
# glibc 2.31 check below.
# Empty since the move off macos-14 (retired 2026-11-02): the darwin-arm64
# artifact is now built AND smoked on macos-15, the core leg, which was
# this broad leg.
@@ -329,8 +330,8 @@ jobs:
# The -portable binary is what all linux install/update paths now deliver;
# it MUST start on old glibc (Debian 11 / RHEL 8 / Ubuntu 20.04). Runs it
# in debian:bullseye (glibc 2.31) — the standard dynamic binary would fail
# here with `GLIBC_2.38 not found`.
# in debian:bullseye (glibc 2.31) — the standard dynamic binary (glibc
# 2.35 floor) would not start here.
- name: Old-glibc compatibility
run: scripts/ci/check-glibc-compat.sh "$RUNNER_TEMP/cbm-artifact/codebase-memory-mcp"
+3 -3
View File
@@ -164,9 +164,9 @@ else
EXT="tar.gz"
fi
# Linux ships a fully-static "-portable" build; the standard linux binary
# dynamically links glibc 2.38+ and fails on older distros (Debian 11, RHEL 8,
# Ubuntu 20.04). macOS/Windows have no such variant.
# Linux ships a fully-static "-portable" build; the standard linux binary is
# built on Ubuntu 22.04, dynamically links glibc 2.35+ and fails on older
# distros (Debian 11, RHEL 8, Ubuntu 20.04). macOS/Windows have no such variant.
PORTABLE=""
[ "$OS" = "linux" ] && PORTABLE="-portable"
+2 -2
View File
@@ -15,8 +15,8 @@ class CodebaseMemoryMcp < Formula
end
end
# The static -portable build: the dynamic one needs glibc 2.38, which
# Ubuntu 22.04, Debian 12 and RHEL 9 do not have.
# The static -portable build: the dynamic one is built on Ubuntu 22.04 and
# needs glibc 2.35, which Debian 11, RHEL 8/9 and Ubuntu 20.04 do not have.
on_linux do
on_arm do
url "https://github.com/DeusData/codebase-memory-mcp/releases/download/v#{version}/codebase-memory-mcp-linux-arm64-portable.tar.gz"
+4 -3
View File
@@ -742,9 +742,10 @@ async function main() {
const source = resolveDownloadBase();
fs.mkdirSync(BIN_DIR, { recursive: true });
// Linux ships a fully-static "-portable" build; the standard linux binary
// dynamically links glibc 2.38+ and fails on older distros. macOS/Windows
// have no such variant. Keep in sync with install.sh / pypi _cli.py / cli.c.
// Linux ships a fully-static "-portable" build; the standard linux binary is
// built on Ubuntu 22.04, dynamically links glibc 2.35+ and fails on older
// distros. macOS/Windows have no such variant. Keep in sync with install.sh /
// pypi _cli.py / cli.c.
const variant = platform === 'linux' ? '-portable' : '';
// No UI/standard split since v0.10.0: one archive per platform, graph UI
// always embedded. The old CBM_VARIANT=ui opt-in pointed at ui-* archives
+4 -3
View File
@@ -1290,9 +1290,10 @@ def _download(version: str) -> Path:
os_name = _os_name()
arch = _arch()
ext = "zip" if os_name == "windows" else "tar.gz"
# Linux ships a fully-static "-portable" build; the standard linux binary
# dynamically links glibc 2.38+ and fails on older distros. macOS/Windows
# have no such variant. Keep in sync with install.sh / install.js / cli.c.
# Linux ships a fully-static "-portable" build; the standard linux binary is
# built on Ubuntu 22.04, dynamically links glibc 2.35+ and fails on older
# distros. macOS/Windows have no such variant. Keep in sync with install.sh /
# install.js / cli.c.
portable = "-portable" if os_name == "linux" else ""
archive = f"codebase-memory-mcp-{os_name}-{arch}{portable}.{ext}"
base, loopback = _download_base(version)
+8 -6
View File
@@ -1,12 +1,14 @@
#!/usr/bin/env bash
# Regression guard: the linux binary we ship must START on OLD glibc.
#
# The standard linux release binary dynamically links glibc 2.38+ / GLIBCXX
# 3.4.32 and fails to start on Debian 11, RHEL/Rocky 8, Ubuntu 20.04, Amazon
# Linux 2, etc. The fix points all linux install + self-update paths at the
# fully-static "-portable" asset. This runs a given linux binary inside an
# old-glibc container (debian:bullseye, glibc 2.31) and asserts it starts:
# - RED for the dynamic standard binary (GLIBC_2.38 not found)
# The standard linux release binary is built in a pinned Ubuntu 22.04 container
# and dynamically links glibc / libstdc++ with a 2.35 / GLIBCXX_3.4.30 floor
# (asserted on the built bytes by check-binary-composition.sh A6), so it fails
# to start on Debian 11, RHEL/Rocky 8, Ubuntu 20.04, Amazon Linux 2, etc. All
# linux install + self-update paths therefore use the fully-static "-portable"
# asset. This runs a given linux binary inside an old-glibc container
# (debian:bullseye, glibc 2.31) and asserts it starts:
# - RED for the dynamic standard binary (GLIBC_2.3x not found)
# - GREEN for the static -portable binary (runs anywhere)
#
# Usage: check-glibc-compat.sh <path-to-linux-binary>
+3 -2
View File
@@ -1256,8 +1256,9 @@ fi
echo "OK: update hands off to $UPDATE_SCRIPT without downloading"
# The glibc constraint did NOT disappear with in-process update -- it moved. The
# standard linux asset dynamically links glibc 2.38+ and breaks on Debian 11,
# RHEL 8 and Ubuntu 20.04, so the installer must fetch the static "-portable"
# standard linux asset is built on Ubuntu 22.04, dynamically links glibc 2.35+
# and breaks on Debian 11, RHEL 8 and Ubuntu 20.04, so the installer must fetch
# the static "-portable"
# build. Guard it where the behaviour now lives instead of retiring the
# protection along with the code that used to implement it.
if [ -f "$REPO_ROOT/install.sh" ]; then
+3 -3
View File
@@ -14358,9 +14358,9 @@ static void build_update_url(char *url, int url_sz, const char *os, const char *
base_url = "https://github.com/DeusData/codebase-memory-mcp/releases/latest/download";
}
/* Linux ships a fully-static "-portable" build; the standard linux binary
* dynamically links glibc 2.38+ and fails on older distros. macOS/Windows
* have no such variant. Keep in sync with install.sh / install.js / pypi
* _cli.py. */
* is built on Ubuntu 22.04, dynamically links glibc 2.35+ and fails on
* older distros. macOS/Windows have no such variant. Keep in sync with
* install.sh / install.js / pypi _cli.py. */
const char *portable = (strcmp(os, "linux") == 0) ? "-portable" : "";
snprintf(url, url_sz, "%s/codebase-memory-mcp-%s-%s%s.%s", base_url, os, arch, portable, ext);
}
+5 -3
View File
@@ -3,9 +3,11 @@
# Mirrors CI's broad-matrix ubuntu-22.04 legs: the OLDEST userland we claim
# the portable (musl-static) binary runs on. Two assertions live here:
# 1. the portable binary passes the full canonical smoke on glibc 2.35;
# 2. the DYNAMIC binary refuses to start (its glibc 2.38+ floor is by
# design — old-glibc coverage is the portable binary's job, and a
# dynamic binary that silently ran here would mask a floor regression).
# 2. the LOCAL dynamic binary (built in the noble test image, glibc 2.39)
# refuses to start — proof that this venue really is an older userland
# than the build image. The RELEASE dynamic binary is built in a pinned
# ubuntu:22.04 container instead and does start on 2.35; its floor is
# asserted on the built bytes by check-binary-composition.sh (A6).
# Pinned by digest like every other venue image: a floating tag means the
# glibc-FLOOR assertion silently drifts to whatever ubuntu:22.04 points at, and
# a floor that moves is not a floor. (Scorecard PinnedDependenciesID #76 — this
+6 -3
View File
@@ -286,7 +286,10 @@ services:
# ── glibc floor (ubuntu-22.04, glibc 2.35) ─────────────────
# Mirrors CI's broad-matrix 22.04 legs: the portable (musl-static) binary
# must pass the canonical smoke on the oldest supported userland, and the
# dynamic binary must REFUSE to start (glibc 2.38+ floor is by design).
# LOCAL dynamic binary (built in the noble test image) must REFUSE to start,
# proving this venue is older than the build image. The release dynamic
# binary builds in a pinned ubuntu:22.04 container instead (2.35 floor,
# asserted by check-binary-composition.sh A6).
smoke-glibc-floor:
build:
context: ..
@@ -301,9 +304,9 @@ services:
file build/alpine/codebase-memory-mcp | grep -q "statically linked"
echo "=== glibc-floor: portable binary smoke on glibc 2.35 ==="
scripts/smoke-local.sh ./build/alpine/codebase-memory-mcp
echo "=== glibc-floor: dynamic binary must refuse (glibc 2.38+ floor) ==="
echo "=== glibc-floor: local noble-built dynamic binary must refuse ==="
if build/linux-arm64/codebase-memory-mcp --version 2>/tmp/dynamic-floor.err; then
echo "ERROR: dynamic binary ran on glibc 2.35 — floor contract broken"
echo "ERROR: a noble-built dynamic binary ran on this venue — it is not older than the build image"
exit 1
fi
grep -qi 'GLIBC' /tmp/dynamic-floor.err