Repository navigation
fix(build): make the vendored libghostty-vt archives reproducible - #668
Merged
Merged
Conversation
The release gate that rebuilds spyc-vt-sys's five archives from the pin and compares checksums has never passed: it failed on v2.2.0-rc.1, so that tag's GitHub release (and brew/apt) never published. Two causes: - zig's archives are not reproducible as built. A Linux one names each member by its absolute path in the build cache, which changes every run; a darwin one's `__.SYMDEF SORTED` header records the build time and the builder's uid/gid. The objects inside are byte-identical across runs, build paths and caches (checked member by member), so the script now re-archives the same members, in the same order, under their basenames, with this zig's own `ar` in deterministic mode. - A shared global zig cache leaks objects from another zig build of the same version (zig keys its cache on the version string): compiler_rt came out different until the script used a private global cache. The committed archives match no fresh build of the documented recipe at all (every object differs in content and size), so they are regenerated here with ziglang.org's zig 0.16.0, the build CI installs. Two full runs from fresh clones at different paths, with fresh caches, produce the same five checksums. The rebuild-and-compare moves into its own reusable workflow, called by release.yml as before and also run on a PR that touches the archives, the pin or the recipe, so a change proves itself before a tag does. Co-Authored-By: Claude <noreply@anthropic.com>
CI's first run of the rebuild-and-compare on this PR reproduced all three Linux archives from the Mac-built bytes, but not the two darwin ones. On a macOS host ghostty's build compiles darwin targets against the installed Xcode SDK (pkg/apple-sdk uses xcrun, and the build also links a dylib with Apple ld, so the SDK can't be hidden); from Linux it uses zig's bundled Darwin headers, which the pinned zig fixes. A Linux arm64 container produced exactly CI's x86_64 bytes for all five, and CI's darwin bytes were already identical between v2.2.0-rc.1's run and this one, so Linux is the canonical host. - The two darwin archives are replaced with the Linux-built ones. The full gate passes on macOS against them, and the pane and engine tests pass for x86_64-apple-darwin under Rosetta. - `make vendor-ghostty` and the script refuse to run off Linux. - `make vendor-ghostty-container` runs the recipe in an ubuntu:24.04 container with ziglang.org's zig (checked against its published sha256), for a Mac. Run from empty vendor outputs, it rebuilt all five to CI's checksums. - The workflow uploads the archives it built, so a mismatch hands over the bytes to commit. Co-Authored-By: Claude <noreply@anthropic.com>
DerekMarshall
added a commit
that referenced
this pull request
Oct 14, 2026
Sets the version to `2.2.0-rc.2`, made with `make release-prep VERSION=2.2.0-rc.2`. Only `Cargo.toml` and `Cargo.lock` change: an rc gets no CHANGELOG section, because the 2.2.0 section will cover the whole cycle. **rc.1 never reached GitHub, brew or apt.** Its release run's archive-reproducibility gate failed, so `Sign + publish release` was skipped. Only crates.io got `spyc 2.2.0-rc.1`. #668 fixed the cause: CI now reproduces the committed `libghostty-vt.a` archives byte for byte, which is the first time that gate has passed. rc.2 is the first rc to go out through every channel. It also carries #667, the two flaky statusline tests that had main's CI red. `main` stays at `2.2.0-rc.2` through the soak. **After merge, on `main`:** 1. `make release-tag VERSION=2.2.0-rc.2` 2. Push the tag. `release.yml` verifies the archives reproduce, publishes a GitHub pre-release, and dispatches the Homebrew tap and apt. The crates job publishes `spyc 2.2.0-rc.2` and skips `spyc-vt-sys 0.1.0`, which is already on crates.io with the older archives (bumping it needs its trusted-publisher entry). **Checks:** `make check-ci` passes, and the binary reports `spyc 2.2.0-rc.2 (<sha>)`. Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The release gate that rebuilds spyc-vt-sys's five
libghostty-vt.aarchives from the pin and compares checksums has never passed. It failed onv2.2.0-rc.1(run 37275888635), soSign + publish releasewas skipped and that tag reached crates.io only: no GitHub release, no brew, no apt. SECURITY.md's "Reproducible by anyone" wasn't true. An rc.2 tagged before this would end the same way.Root cause
Four findings, each checked member by member with a read-only
arparser:<work>/zc-<target>/o/<hash>/vt.o), and the work directory is a freshmktempevery run.__.SYMDEF SORTEDheader records the build's wall-clock time and the builder's uid/gid.~/.cache/zighanded one build acompiler_rt.othat some other "0.16.0" had made. A fresh one is deterministic at any path.pkg/apple-sdkusesxcrunthere, and the build also links a dylib with Appleld, so the SDK can't be hidden. From Linux it uses zig's bundled Darwin headers, which the pinned zig fixes. A Mac build therefore follows whatever Xcode is installed.Fix
make vendor-ghosttyand the script refuse to run anywhere else.make vendor-ghostty-containerruns the recipe in anubuntu:24.04container (podman or docker) with ziglang.org's zig, checked against its published sha256. This is the Mac path.ar rcsD(--format=darwinorgnu). It refuses two members that share a basename.ZIG_GLOBAL_CACHE_DIRunder the script's work directory.CHECKSUMSregenerated. The pin and the bindings are unchanged..github/workflows/vendored-archives.yml:publish.REQUIRED_ZIG, either script or the Makefile, plusworkflow_dispatch.use-cache: falseon setup-zig, since the recipe uses its own cache.Evidence
The same five checksums came out of four independent builds:
make vendor-ghostty-containerfrom empty vendor outputsThe two native Mac runs matched each other and CI for every Linux target, but not for the darwin targets, which is what exposed cause 3.
Testing against the new archives:
make check-ci:=== GATE: PASS ===, 3,176 library tests on macOS (Linux-built darwin archive), including the ghostty engine suite.x86_64-apple-darwinunder Rosetta.make lint-linux,make package-checkandmake lint-workflowspass.A trap caught along the way: the container script first fed itself to
bash -son stdin,apt-getswallowed the rest of it, and it "passed" having built nothing. The archives' modification times gave it away. It now passes the script as an argument, and the re-test started from deleted outputs, so it couldn't pass by doing nothing.Not in this PR
spyc-vt-sys 0.1.0is already on crates.io with the old archives, and the crates job skips a version that's already published. crates.io consumers keep those bytes until the crate is bumped to0.1.1, which needs the trusted-publisher entry forspyc-vt-sysfirst.Generated with Claude Code