GitHub

Upgrading Dependencies

The build system pins every external tool so that a build is reproducible on any machine. This page covers what is pinned, how to upgrade it, and the two cases that need a code change before a bump is even possible.

What is pinned, and where

Kind Pinned in Integrity
Binary releases, downloaded rather than compiled a std.SemanticVersion constant in build.zig, plus a per platform table in the matching resolve_<tool>_checksum function one Zig package tree hash per operating system and architecture
Git sources, compiled from source a .url and .hash entry in build.zig.zon one Zig package tree hash for the resolved commit
Cloned tools a version constant in build.zig passed as the clone’s --branch none, so the pin is a tag name only

The Zig toolchain is pinned separately, in zig/download.sh and zig/download.win.ps1, and is not covered here. Moving Zig is a standard library migration, not a dependency bump.

For the current pins and their versions, run the inventory script. It reads build.zig and build.zig.zon with the Zig toolchain’s own parser, so it is always in step with the source:

./zig/zig run .claude/skills/upgrade-deps/scripts/list_deps.zig

No table of current versions is kept in this document on purpose. Any such table goes stale the first time somebody bumps a pin without editing docs, and this page has been stale that way before.

The hashes are not upstream checksums

The N-V-... strings in build.zig and build.zig.zon are Zig package tree hashes over the unpacked archive, not the sha256 sums upstream publishes. Only zig fetch produces them, so copying a digest out of a checksums file will not work.

The build also short circuits on a warm cache. If the archive for the old hash is still in the global Zig cache, a version change with an unchanged hash keeps the old binary and the build still passes. Every version bump has to change its hashes to be verified at all.

Upgrading with the skill

Inside Claude Code, run /upgrade-deps. It runs the inventory script, checks each dependency against its upstream, applies a fourteen day release cooldown (the same supply chain quarantine renovate.json already uses for GitHub Actions), applies every bump that cleared it to the current working tree at once, and runs the repository’s full verification list on the combined change: install, each bumped tool’s own build step and --version, check, the tidy tests, test --summary all, the cfo typecheck and tests, docs -Dtest, the -Ddry-run wiring sweep as smoke tests for every remote-acting build step (release, docker, the deploys, the scripts runner; nothing remote runs), and a build-time regression check against a baseline taken before the edits. If one dependency breaks a gate, the skill reverts that one and reports it, keeping the rest.

By default the verified changes are left uncommitted in the tree for you to test; git diff shows them and git checkout -- build.zig build.zig.zon discards them. The pins must be clean before a run, so a tree still holding the last run’s changes is reported and left untouched instead of being applied to twice. The skill installs the toolchain itself when ./zig/zig is missing, following the setup page, and never asks a question: every condition it can meet has a rule and an outcome in its report, because it is meant to run on a schedule. Pass --submit to have each dependency committed on its own branch off plan and opened as an independent pull request with gh, nothing outside this repository required. Pass --report-only to see what is outdated without changing anything, or name a single dependency to restrict the run to it.

Scheduled runs

The workflow Upgrade Dependencies (.github/workflows/upgrade-deps.yml) runs the skill on a schedule, Mondays at 12:00 UTC. It runs on linux-aj, an on-prem guest set aside for this job alone; see Dedicated runner for upgrade-deps for how that runner is set up and isolated.

The job draws on two repository secrets. AJ_CLAUDE_CODE_OAUTH_TOKEN is AJ’s Claude subscription token, minted with claude setup-token and good for one year. HB_DEV_AJ_GITHUB_TOKEN is a fine-grained personal access token on the hb-dev-aj account, valid for up to 366 days. The job holds no cloud credential: the skill’s verification stops at the offline gates, and the pull requests it opens get the real Cloud Build plans as status checks.

To run it by hand, use the Actions tab and dispatch the workflow, choosing a mode of report-only, apply or submit and, optionally, a single dependency to restrict the run to. The skill’s final report appears in the job summary, not as a separate artifact.

Pull requests from a scheduled or dispatched run arrive authored by hb-dev-aj, the same as a manual --submit run, and still need one review and the required status checks before merging.

Every credential the job uses is wiped at the end, whether the job succeeds, fails, or is cancelled: an always() step in the workflow and a runner hook both run the wipe, and a verify step fails the job if anything, credential or process, is still on the box afterward.

Upgrading by hand

These are the same commands the skill runs.

A binary release

Edit the version constant in build.zig. For each platform URL in the tool’s download function, substitute the new version and fetch it:

./zig/zig fetch <url>

Each call prints one hash. Paste each into the matching arm of resolve_<tool>_checksum, matching the platform the URL was for. Then build the step that consumes the tool and run it:

./zig/zig build install
./bin/<tool> --version

Not every tool is part of install. Pandoc is built by the docs step and ShellCheck by the shellcheck step, so those need their own build command.

A git source

From the repository root:

./zig/zig fetch --save=<name> "git+https://github.com/<owner>/<repo>#<tag>"

Zig rewrites the entry in build.zig.zon, moving the tag into a ?ref= query and replacing the fragment with the commit it resolved. Confirm that commit really is the tag’s commit, because a URL carrying only a ?ref= query and no fragment resolves to the default branch while the text still claims the tag:

./bin/gh api repos/<owner>/<repo>/git/ref/tags/<tag> --jq '.object.type + " " + .object.sha'

An .object.type of tag means an annotated tag, so peel it through git/tags/<sha> to get the commit. Then check the tree resolves and build:

./zig/zig build --fetch
./zig/zig build <step>
./bin/<tool> --version

Do not use --save-exact. It leaves the tag in the fragment rather than a commit, which is a moving target, not a pin.

A known caveat

jq stamps its version from the pin text. JQ_VERSION is read out of the ?ref= text in build.zig.zon at compile time, and jq tags its releases jq-X.Y.Z. Until the define strips that prefix, ./bin/jq --version prints the tag verbatim rather than a bare version number.

Ship it

One pull request per dependency, based on plan, titled chore: upgrade <tool> to <version>. Say in the body why the bump was taken, how the hashes were regenerated, which gates ran and their results, and which platforms were verified locally. Continuous integration covers linux and windows; macOS hashes can only be exercised on a Mac. The skill’s --submit mode produces exactly this shape; by hand, commit each dependency separately and open each pull request from its own branch.

Edit this page