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.zigNo 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> --versionNot 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> --versionDo 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.