Skip to content

Make the Linux release run on older distributions - #34

Merged
jserv merged 2 commits into
mainfrom
linux-compat
Sep 9, 2026
Merged

Make the Linux release run on older distributions#34
jserv merged 2 commits into
mainfrom
linux-compat

Conversation

@jserv

@jserv jserv commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

The published codetrial-x86_64-unknown-linux-gnu carried symbol requirements up to glibc 2.39, because it was built on the runner, so it refused to start on Ubuntu 20.04 and on anything older than that runner. The Linux leg now compiles inside Bullseye, which carries glibc 2.31, and two checks keep the floor there: the symbol versions the binary asks for, capped for both glibc and libstdc++, and the binary actually starting on that userland before anything is published. It stays an ordinary dynamically linked executable rather than moving to musl. The cost is a compiler pinned by hand, since this leg no longer builds with whatever stable the gates ran.

The second commit is a gap found while working on the first. scripts/test.sh has an actionlint lane that skips when the binary is absent, and CI never installed it, so the file describing every other check was the one file nothing checked. CI installs it pinned now, a checkout without the binary falls back to the same version's container image, and the two tools this workflow fetches from a GitHub release share one script, so the digest check exists once rather than twice.

Verified by running the release steps rather than reading them. The three Linux steps were extracted verbatim from the workflow and executed against a scratch copy of the tree under the same flags GitHub uses for shell: bash: the build succeeds, everything the container writes is owned by the invoking user, the symbol check passes, and the binary starts on debian:bullseye-slim. The binary it produces needs GLIBC_2.30 and GLIBCXX_3.4.26 against floors of 2.31 and 3.4.28, and runs --help on ubuntu:20.04, which is the environment from the report. Negative controls: a 2.28 baseline fails with the version it found, and a non-ELF file trips the linkage guard. The shared installer was exercised end to end with a shim for sudo -- a missing asset exits 1, a real one verifies its checksum and extracts. ./scripts/test.sh passes with no lane reporting a skip, which is itself new: actionlint now runs.

Left out deliberately: nothing checks that the pinned compiler still builds this crate until after a merge, because a pull-request-time container build would cost a cold compile on every push. The first cache-cold run of the Linux release leg is the thing to watch against that job's 45 minute timeout.

Closes #30


Summary by cubic

Fixes #30 by building the published Linux release in Debian Bullseye instead of the CI runner. This lowers the compatibility baseline from glibc 2.39 to glibc 2.31 so the dynamically linked binary runs on Ubuntu 20.04 and newer supported distributions, while release checks reject newer glibc or libstdc++ requirements and failed startup on Bullseye.

Bug Fixes

  • Pins the Linux release compiler to rust:1.98.0-bullseye, so compiler updates now require a manual workflow change.
  • Documents source builds as the path for distributions older than the release baseline.

New Features

  • Installs pinned actionlint in CI so workflow checks no longer silently skip.
  • Uses the matching actionlint container image for local checks when the binary is unavailable and Docker is running.
  • Centralizes release-tool downloads in a checksum-verifying installer used by both actionlint and commentflow.

Written for commit 4a7994b. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

The published binary carried symbol requirements up to glibc 2.39, which
is the runner's, so it refused to start on Ubuntu 20.04 and on every
distribution older than the machine that built it. Compiling it in a
Bullseye container puts the floor back at the oldest release this
project intends to run on, and two checks keep it there: the symbol
versions the binary asks for, and the loader on that userland actually
starting it.

The cost is a compiler pinned by hand, so the cache entry is named after
that image rather than after the runner. Without it a target directory
filled by the old runner build would survive under the old key, and a
pin bumped to the runner's stable would let cargo call those objects
fresh and link C compiled against a newer glibc into a Bullseye binary.
The actionlint lane skips when the binary is absent, and CI never
installed it, so the file describing every other check was the one file
nothing checked. CI installs it pinned now, and a checkout without the
binary takes the same version's container image rather than reporting a
green gate over an unread workflow.

That is a second tool fetched from a release, and the digest guard is
the part worth having exactly once, so both fetches now go through one
script. The README and the development notes said CI installed all of
these; for this one they were describing a lane that never ran.
@jserv
jserv merged commit 39b05ff into main Sep 9, 2026
6 checks passed
@jserv
jserv deleted the linux-compat branch September 9, 2026 13:48
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.

Pre-built Linux binary requires glibc 2.39, fails on older distros

1 participant