Skip to content

fix(linux): bundle correct uv binary SHA256 so AppImage launches#850

Open
itomek wants to merge 1 commit intomainfrom
fix/appimage-uv-binary-sha
Open

fix(linux): bundle correct uv binary SHA256 so AppImage launches#850
itomek wants to merge 1 commit intomainfrom
fix/appimage-uv-binary-sha

Conversation

@itomek
Copy link
Copy Markdown
Collaborator

@itomek itomek commented Apr 22, 2026

Summary

Follow-up fix for #782 / #847. On a fresh Ubuntu 24.04.4 LTS host the 0.17.4 AppImage aborts bootstrap with:

```
state: failed (ensure-uv)
[main] Backend bootstrap failed: Bundled uv resource SHA256 mismatch
expected 22034760075b92487b326da5aa1a2a3e1917e2e766c12c0fd466fccda77013c7
got 0e05d828b5708e8a927724124db3746396afddad6273c47283d7c562dc795bd6
```

The same error is reproducible in the post-merge CI run for #847 (run 24797051764) — both `AppImage distro matrix` and `AppImage userns-restricted` jobs fail identically; the PR merged with red Linux launch checks.

Fixes #849.

Root cause

`BUNDLED_UV_SHA256["linux-x64"]` was set to the tarball SHA256 (value published at astral-sh/uv's .sha256 and verified by the workflow when downloading the `.tar.gz`). But `ensureUv()` hashes the extracted ELF at `resources/vendor/uv/linux-x64/uv`. Those are two different digests by definition — the constant can never match.

Changes

  • `src/gaia/apps/webui/services/backend-installer.cjs` — set `BUNDLED_UV_SHA256["linux-x64"]` to the binary SHA256 (`0e05d828…`). Rewrite the "update BOTH" comment so future uv bumps use the correct digest in each place (workflow pin = archive SHA; this constant = binary SHA).
  • `tests/electron/appimage-smoke.test.mjs` — new AC4/T3b assertion hashes the extracted uv and compares it to `BUNDLED_UV_SHA256["linux-x64"]`. This catches future hash drift at the 12s structural-smoke stage instead of the 2-3 minute distro-matrix runs (the prior smoke only asserted file existence and the exec bit, which is why fix(linux): AppImage launch on Linux (#782) #847 merged red).

Verification

On Ubuntu 24.04.4 LTS with the 0.17.4 AppImage downloaded from #847's build artifacts:

  • Pre-fix: AppImage aborts 150 ms after launch at `state: failed (ensure-uv)` with the mismatch in `/.gaia/electron-main.log`.
  • Post-fix smoke: `AC4/T3b: bundled uv SHA256 matches BUNDLED_UV_SHA256[linux-x64]` passes.
  • Regression check: flipping the constant back to the archive SHA makes `AC4/T3b` fail with a clear message ("ensureUv() will reject this at runtime") — confirms the new test actually catches this class of bug.

Test plan

  • CI: `appimage-structural-smoke` — new AC4/T3b passes; pre-existing checks unchanged.
  • CI: `appimage-distro-matrix` — reaches `state: ready` on ubuntu24-libfuse, ubuntu24-without-libfuse (humane error), fedora41.
  • CI: `appimage-userns-restricted` — reaches `state: ready` under `kernel.apparmor_restrict_unprivileged_userns=1`.
  • Manual: launch the built AppImage on Ubuntu 24.04 minimal; backend binds a random port; `/api/health` returns 200.

Notes for reviewers

  • The binary hash (`0e05d828b5708e8a927724124db3746396afddad6273c47283d7c562dc795bd6`) is deterministic for uv v0.5.14's published `uv-x86_64-unknown-linux-gnu` ELF; it can be reproduced locally via `sha256sum` of the file extracted from the published tarball, or computed in CI with an added step and diffed against this constant if you want an anchored chain of custody.
  • No changes to the archive pin in `build-installers.yml` — that value is correct for the archive download step.

ensureUv() hashes the extracted `uv` ELF at runtime, but
BUNDLED_UV_SHA256["linux-x64"] was set to the uv .tar.gz archive SHA
(the value published at astral-sh/uv's .sha256 and verified by CI when
downloading the tarball). The archive digest and the extracted-binary
digest are two different values by definition, so every Linux AppImage
launch aborted at `state: ensure-uv` with "Bundled uv resource SHA256
mismatch".

Set the constant to the actual binary SHA256 and correct the comment
so future uv bumps update the two distinct digests in their correct
places (workflow = archive, constant = binary).

Also add a smoke-test assertion that hashes the bundled uv and compares
it to BUNDLED_UV_SHA256 — the prior smoke only checked existence and
the exec bit, which is why this regression reached main.

Verified on Ubuntu 24.04.4 LTS: pre-fix the 0.17.4 AppImage aborts at
ensure-uv with the logged mismatch; post-fix the smoke test reports the
binary hash matches the constant. The same mismatch caused the AppImage
distro-matrix and userns-restricted jobs on PR #847's merge commit to
fail (run 24797051764).

Fixes #849.
@itomek itomek requested a review from kovtcharov-amd as a code owner April 22, 2026 22:20
@github-actions github-actions Bot added the tests Test changes label Apr 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

Small, surgical fix that corrects a genuine launch-blocking bug: BUNDLED_UV_SHA256["linux-x64"] was storing the tarball digest (the value the workflow verifies after curl), but ensureUv() hashes the extracted ELF via sha256File() at src/gaia/apps/webui/services/backend-installer.cjs:816,839, so the two could never match and every fresh AppImage launch died at state: failed (ensure-uv). Confirmed: .github/workflows/build-installers.yml:217-227 pins the archive SHA 22034760…, extracts uv out of the tarball, and ships only the ELF — hashing that ELF at runtime yields the 0e05d828… now stored here. The added AC4/T3b smoke test meaningfully raises the bar: the prior AC4/T3 only checked existence + exec bit, which is exactly why #847 merged red.

Issues Found

None blocking. One optional nit:

🟢 Cross-platform path handling (tests/electron/appimage-smoke.test.mjs:158)new URL(import.meta.url).pathname returns a leading-slashed path on Windows (e.g. /C:/...). This test is Linux-only in practice (gated by GAIA_APPIMAGE), so it's not a bug today. For future-proofing if this file ever grows Windows/macOS smoke tests, fileURLToPath is the idiomatic choice:

        const installerPath = path.resolve(
          path.dirname(require("node:url").fileURLToPath(import.meta.url)),
          "..",
          "..",
          "src",
          "gaia",
          "apps",
          "webui",
          "services",
          "backend-installer.cjs"
        );

(Or add import { fileURLToPath } from "node:url"; at the top and use it directly — matches the ESM style of the rest of the file.)

Strengths

  • Root cause isolated cleanly. No drive-by edits — exactly the constant + comment + regression test, nothing else. The scope-clean discipline CLAUDE.md asks for.
  • The new test is a real regression trap, not a rubber stamp. Parsing the constant out of backend-installer.cjs and hashing the AppImage-extracted ELF means any future uv bump that forgets one of the two digests fails the 12 s structural smoke instead of the 3-minute distro matrix — and the PR description verifies the test actually fires by flipping the constant back, which is the right way to prove a guard-rail works.
  • Comment update is load-bearing. Rewriting the "update BOTH" block to name the two distinct digests (archive SHA in the workflow, binary SHA here) removes the documentation ambiguity that led to the bug in the first place.

Verdict

Approve. Already approved by @kovtcharov-amd. Safe to merge as-is; the fileURLToPath note is strictly optional and can be deferred until this test grows to cover other platforms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: AppImage fails to launch — bundled uv SHA256 pin is the tarball hash, not the binary hash

2 participants