Skip to content

Commit 81e68e1

Browse files
BinHsuclaude
andcommitted
fix(engine): cc-debian13 base — runtime glibc must be >= build glibc (WS2-2)
The distroless_cc flip got the engine past the missing loader, but it then crashed with `GLIBC_2.38 not found` / `GLIBCXX_3.4.32 not found`: the binary, built on ubuntu-24.04-arm (glibc 2.39, GCC 13), needs symbol versions newer than cc-debian12 (glibc 2.36) ships. local_config_cc is non-hermetic, so the binary inherits the CI runner's glibc. glibc is forward-compatible only -> the runtime base must be >= the build glibc. "Build on an older runner" does NOT work here: the Bazel remote cache reused the 24.04-built objects across runner versions (same non-hermetic action hash), so the binary stayed glibc-2.39. Moving the base FORWARD is the sound fix. - MODULE.bazel: distroless_cc -> cc-debian13:nonroot (glibc 2.41 / GCC 14), re-pinned digest. - release-onprem-image.yml: revert runner to ubuntu-24.04-arm; document the "runtime base glibc >= build glibc" rule. - BUILD.bazel + ADR-0025: record the debian13 correction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 02a6896 commit 81e68e1

4 files changed

Lines changed: 48 additions & 28 deletions

File tree

.github/workflows/release-onprem-image.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ name: Release on-prem image to GHCR
1515
#
1616
# WHY a native arm64 runner:
1717
# The local Talos cluster runs on apple/container = linux/arm64. An amd64
18-
# image (the ubuntu-latest default) would not run there. ubuntu-22.04-arm is
18+
# image (the ubuntu-latest default) would not run there. ubuntu-24.04-arm is
1919
# GitHub's free arm64 Linux runner for public repos. local_config_cc
2020
# auto-detects the runner's arm64 GCC — no toolchain / sysroot setup, no
2121
# BUILD changes. This is also why the engine build belongs in CI, not on the
2222
# 16GB dev host: the grpc + BoringSSL + whisper.cpp/ggml/llama.cpp link line
2323
# needs more RAM than that host can spare for a build VM (de-risked 2026-06-16).
2424
#
25-
# WHY 22.04, not 24.04 (resolved 2026-06-16, WS2-2 live verify):
26-
# The runtime base is distroless cc-debian12 (Debian 12, glibc 2.36). A binary
27-
# built on ubuntu-24.04 (glibc 2.39, GCC 13 / GLIBCXX_3.4.32) references
28-
# symbol versions newer than Debian 12 provides — the engine crashed at
29-
# startup with `GLIBC_2.38 not found` / `GLIBCXX_3.4.32 not found`. glibc is
30-
# forward-compatible, not backward: build against an OLDER glibc than the
31-
# runtime base. ubuntu-22.04 ships glibc 2.35 (<= 2.36) and a libstdc++ no
32-
# newer than Debian 12's, so the binary runs. Keep build glibc <= runtime base.
25+
# RUNTIME-BASE glibc must be >= the build glibc (resolved 2026-06-16, WS2-2):
26+
# The engine binary is dynamically linked and local_config_cc is non-hermetic,
27+
# so it inherits the runner's glibc (ubuntu-24.04-arm = glibc 2.39, GCC 13 /
28+
# GLIBCXX_3.4.32). glibc is forward-compatible, not backward — the runtime
29+
# base must therefore ship glibc >= 2.39. distroless cc-debian12 (glibc 2.36)
30+
# was too old: the engine crashed at startup with `GLIBC_2.38 not found` /
31+
# `GLIBCXX_3.4.32 not found`. The base is now cc-debian13 (Debian 13, glibc
32+
# 2.41 / GCC 14) — see packaging/engine/BUILD.bazel + MODULE.bazel.
3333
#
3434
# Manual trigger only for now (local-first; the operator pulls on demand). A
3535
# push trigger can be added once the WS2 on-prem flow stabilises.
@@ -56,7 +56,7 @@ env:
5656
jobs:
5757
push-onprem-image:
5858
name: Build + push core images to GHCR (linux/arm64)
59-
runs-on: ubuntu-22.04-arm
59+
runs-on: ubuntu-24.04-arm
6060
timeout-minutes: 45
6161
steps:
6262
- name: Checkout

MODULE.bazel

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -295,21 +295,28 @@ use_repo(
295295
"distroless_static_linux_arm64_v8",
296296
)
297297

298-
# Distroless cc-debian12:nonroot — the base for the C++ ENGINE image (the Go
298+
# Distroless cc-debian13:nonroot — the base for the C++ ENGINE image (the Go
299299
# gateway stays on distroless_static). The engine's link line (grpc++ + BoringSSL
300300
# + protobuf + whisper.cpp/ggml/llama.cpp via rules_foreign_cc CMake + libopus)
301301
# produces a DYNAMICALLY-linked binary — the CMake recipes don't honour -static,
302302
# so the binary needs an ELF loader + glibc + libstdc++/libgcc_s at runtime.
303303
# distroless_static ships none of those: the engine image built on it failed at
304304
# `exec /usr/local/bin/engine: no such file or directory` (missing PT_INTERP)
305-
# the first time it was actually run (WS2-2 live verify, 2026-06-16). cc-debian12
306-
# is Google's designated base for dynamically-linked C/C++ — it adds the loader,
307-
# glibc, libstdc++, and libgcc_s that `base-debian12` (glibc only) still lacks.
308-
# Pinned by manifest-list digest (resolved 2026-06-16); re-pin on bump.
305+
# the first time it was actually run (WS2-2 live verify, 2026-06-16). cc-* is
306+
# Google's designated base for dynamically-linked C/C++ (loader + glibc +
307+
# libstdc++ + libgcc_s; base-* has glibc only).
308+
#
309+
# WHY debian13, not debian12: local_config_cc is non-hermetic, so the binary
310+
# inherits the CI runner's glibc (ubuntu-24.04-arm = glibc 2.39). glibc is
311+
# forward-compatible only — the runtime base must be >= the build glibc.
312+
# cc-debian12 (glibc 2.36) was too old (`GLIBC_2.38 not found` at startup);
313+
# cc-debian13 ships glibc 2.41 / GCC 14 libstdc++. Pinned by manifest-list
314+
# digest (resolved 2026-06-16); re-pin on bump. If the build runner ever moves
315+
# to a newer glibc than debian13, bump this base in lockstep.
309316
oci.pull(
310317
name = "distroless_cc",
311-
digest = "sha256:b0ae8e989418b458e0f25489bc3be523718938a2b70864cc0f6a00af1ddbd985",
312-
image = "gcr.io/distroless/cc-debian12",
318+
digest = "sha256:d3cda6e91129130d7229a1806b6a73d292ef245ab032da7851907798024cefba",
319+
image = "gcr.io/distroless/cc-debian13",
313320
platforms = [
314321
"linux/amd64",
315322
"linux/arm64/v8",

docs/adr/0025-oci-packaging-strategy.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,20 @@ at `exec /usr/local/bin/engine: no such file or directory`. The 79 MB binary
193193
was present and arm64-correct; ENOENT-on-exec means a missing ELF interpreter
194194
— i.e. the binary is dynamically linked (rules_foreign_cc CMake deps don't
195195
honour -static, as the fork-point comment anticipated) and `static-debian12`
196-
ships no loader. The fix is `cc-debian12`, NOT the `base-debian12` the
197-
fork-point originally named: a dynamic C++ binary also needs `libstdc++` +
198-
`libgcc_s`, which `base` (glibc only) lacks; `cc` is Google's designated base
199-
for dynamically-linked C/C++. The gateway (Go, genuinely static) stays on
200-
`static-debian12`. Net cost of the lost bet: one extra CI build cycle, exactly
201-
as this decision's "cost of trying-and-failing" line predicted.
196+
ships no loader. The fix is `cc-debian13`, NOT the `base-debian12` the
197+
fork-point originally named. Two corrections: (1) a dynamic C++ binary needs
198+
`libstdc++` + `libgcc_s`, which `base` (glibc only) lacks — `cc` is Google's
199+
designated base for dynamically-linked C/C++; (2) the cc variant must be new
200+
enough. `local_config_cc` is non-hermetic, so the binary inherits the CI
201+
runner's glibc (ubuntu-24.04-arm = glibc 2.39). glibc is forward-compatible
202+
only, so `cc-debian12` (glibc 2.36) still failed with `GLIBC_2.38 not found`;
203+
`cc-debian13` (glibc 2.41 / GCC 14) satisfies it. Rule: runtime base glibc >=
204+
build-runner glibc. (The non-hermetic cache makes this a property of the runner,
205+
not the source — a Bazel remote-cache hit reused the 24.04-built objects across
206+
runner versions, so "build on an older runner" did NOT work; moving the base
207+
forward did.) The gateway (Go, genuinely static) stays on `static-debian12`.
208+
Net cost of the lost bet: a couple of CI build cycles, as this decision's "cost
209+
of trying-and-failing" line predicted.
202210

203211
Original reasoning (kept for the trail):
204212
- The base image was already pulled in `MODULE.bazel` for the gateway

packaging/engine/BUILD.bazel

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,16 @@
4040
#
4141
# The fix is `@distroless_cc`, NOT the `@distroless_base` this comment
4242
# used to suggest: a dynamically-linked C++ binary needs libstdc++ +
43-
# libgcc_s, which `base-debian12` (glibc only) lacks. cc-debian12 is
44-
# Google's designated base for dynamically-linked C/C++ (loader + glibc
45-
# + libstdc++ + libgcc_s). Pulled in MODULE.bazel. This BUILD's comment
46-
# block is the ground-truth fork point — kept in sync with ADR-0025
47-
# §"Slice 4 distroless variant decision".
43+
# libgcc_s, which `base-*` (glibc only) lacks. cc-* is Google's designated
44+
# base for dynamically-linked C/C++ (loader + glibc + libstdc++ + libgcc_s).
45+
#
46+
# The cc variant must also be NEW ENOUGH: local_config_cc is non-hermetic,
47+
# so the binary inherits the CI runner's glibc (ubuntu-24.04-arm = 2.39).
48+
# glibc is forward-compatible only, so cc-debian12 (glibc 2.36) was too old
49+
# (`GLIBC_2.38 not found`); the base is cc-debian13 (glibc 2.41). Keep the
50+
# runtime base glibc >= the build-runner glibc. Pulled in MODULE.bazel.
51+
# This BUILD's comment block is the ground-truth fork point — kept in sync
52+
# with ADR-0025 §"Slice 4 distroless variant decision".
4853
#
4954
# 3. Models NOT shipped in image. Engine reads model path via
5055
# `AEGIS_MODEL_PATH` env var (`engine_cpp/cmd/engine/main.cc:68-73`)

0 commit comments

Comments
 (0)