fork-is-sandboxed has failed every run of this repository since 600a2d1
added the review job without it ("5 job(s), 4 carry HANZO_CI_TRUSTED"),
so no ci or git-runner image has published since. The job already skips
a fork; its credentialed step now carries the same guard as every other.
All 31 entries of hanzo.yml's test block pass locally.
A forge runner lives one job and began each with an empty act cache, and
act clones an action with go-git, full history, before the first step:
forge run 16634 spent 156 of each job's 161 s of setup cloning
setup-node (72 s), setup-go (42 s), setup-bun (20 s) and checkout
(17 s), the three it then skipped included.
The image now clones each into ~/.cache/act/<sha256 of the uses string>
with every ref the remote advertises, so the runner opens it and fetches
what moved since the build. Timed with act's own clone executor: 0.2 to
1.1 s each, against 2 to 90 s cold; with the branches and tags alone it
still fetched every pull request's objects, 10 to 40 s each. It adds
some 340 MB to the image.
hanzo.yml's runner-preseeds fails when build.yml names an action the
image does not carry.
The registry reads ghcr.io for any name it does not hold, so an image
pushed to ghcr.io/<org>/<app> is already oci.hanzo.ai/<org>/<app>, by
digest and by tag. The second copy, and the login that armed it, go.
The pins this run prints name oci.hanzo.ai, where the fleet pulls.
The gate forked grep twice for every tracked file. On hanzo-inc/platform
(1,286 files) that was 26 s here and 80 s on a forge runner, where every
fork crosses the sandbox (run 16634, Structural invariants), and 45 s on
hanzo-inc/cloud. Each rule now runs once over the whole list and only the
lines it matches are read one at a time: 0.1 s and 0.4 s.
The findings are the same lines: old and new agree on platform, app, ci,
universe (11 findings, exit 1 both), cloud and hanzo.ai, and
certclaims_test.sh passes. grep takes --null, not -Z, which BSD grep
reads as --decompress.
The twenty sampled assets were asked one after another, each a round trip
through the edge to the origin: 10.4 s from dgx, seconds of every publish
from a runner. They go out together now, and one that does not answer still
sends everything. Hashing runs one batch per core into a file of its own,
sorted after, so the record is the same bytes in any order. A last line,
`timing:`, names each phase's seconds.
Hosts whose plane never completes a copy (SITE_COPY_FROM) shipped pages without
their assets, so callers split one export into a step per host and published
them in series. With copy: false each site uploads its own files and all of them
run in parallel. The default keeps the copy.
gate.sh exited 1 when KMS refused the CI credential or held no
review/IAM_CLIENT_ID for the org, and cicd needs review, so every
caller of build.yml published nothing from 11:18Z (hanzoai/console
8fa5c86b23 and f83eecedeb). A missing identity is a fact about the CI
credential, not a view of the change: both now warn that the reviewer
did not run and exit 0, as an unreachable store already did. A reviewer
that runs and refuses still exits 1.
review_test.sh drives gate.sh against a stub store for both refusals,
and through the gate against a refusing reviewer.
The README's caller named .hanzo/workflows/build.yml@v1, a path that
resolves nothing, and granted no permissions, so a repo started from it
failed at startup once build.yml declared contents, packages and
id-token: write. The caller shown now names .github/workflows/build.yml@v2
and grants those three on its job.
Every save archived the whole build cache it restored plus what it
compiled, so nothing ever left it. hanzo-inc/cloud's four gate shares
grew to 7-8.5 GiB of zstd, 27 GiB unpacked; the runner pods restoring
them were evicted at their 80Gi ephemeral-storage limit mid-gate, and
every release since run 1009 was skipped.
Restore now stamps each restored entry two hours old, past the hour in
which Go leaves a used entry's mtime alone, and save drops every entry
still older than the restore. The cut archives live under the name
suffix -used, so none written before is restored again.
A stalled git.hanzo.ai gives up in twenty seconds and the run
continues on github.com. Publishing waits for the review. The
tests do not.
Testing: bash bin/get_test.sh && bash .hanzo/review/review_test.sh
Risk: high. A refused diff publishes nothing. A reviewer that cannot be reached still publishes.
How to revert: git revert this commit and move the v2 tag back to 5262d63804.
The client lane's cut rewrote the version in Cargo.toml and left the
crate's own entry in Cargo.lock at the old number, so every `--locked`
build of the cut failed: hanzoai/cli 8.5.729 and 8.5.730 published no
binaries. The cut now moves that one entry, the crate's own, and refuses
when the lock does not name the new version afterwards.
Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
ignoretracked, modsize, conflictmarkers, vendormark and certclaims are
facts about the whole repository, so a call naming a share runs them
only in the share that gates on "everything but" (`!`). certclaims alone
took 5 of hanzo-inc/cloud's minutes in each of its five shares.
Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
Hanzo Git sets no GITHUB_WORKFLOW_REF, so the step could not find the
caller's workflow file and fetched the tools at the moving `v1`. A caller
pinned to a newer release then ran its steps against older tools:
hanzo-inc/cloud at v1.0.128 failed in three minutes, "missing: cache".
With the file unknown, the pin is the one this repository's workflows
all name, when they name exactly one; otherwise nothing changes.
Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
The share that names the others' entries with a leading `!` runs the
rest, so a test entry added to hanzo.yml later is gated by default
instead of belonging to no share.
Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
A call naming `gates` skips the publish job: what it would publish is the
whole repository's, and five parallel shares would each spend a runner on
it. hanzo-inc/cloud's publish job has nothing to publish and took three
to six minutes of setup after the gate.
Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
A gate too long for one runner can now run as parallel jobs, each naming
its share of the `test:` entries; hanzo.yml stays the one list, and a name
it does not declare is refused rather than dropped. hanzo-inc/cloud's gate
is one job of 142 minutes: its fleet check alone takes 58 and its unit
tests 45, and nothing orders them.
Each share keeps its own Go cache key, so parallel jobs each save what
they compiled instead of one finding the key another already took.
Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
A client preview answers 404 to everyone until its client signs off, so the
public read-back cannot reach its bytes. On a 404, and only a 404, the
release is read back from cloud with the credential that published it: the
deployment this run completed must be live, hold the files and bytes this
run sent, and be the one the project serves. Any other answer from the host
still fails the deploy. The hanzo.id redirect a forward-auth gave is no
longer accepted.
Co-authored-by: Hanzo Dev <[email protected]>
The reusable asks for id-token: write and exchanges the run's Actions ID token
at IAM for the CI identity its repository's account speaks for, so no GitHub
repository or org needs KMS_CLIENT_ID. bin/kms, and the site action through it,
does the same. A stored client remains the credential on Hanzo Git, which
issues no ID token.
The automatic token reads only a package its repository was granted, so a
caller with no GH_PAT whose image is an org package it was never linked to
read nothing published, and with no declared version it had no version at
all. The KMS GitHub token is the rung the registry logins already take
between the two, and imgver now climbs the same ladder.
The KMS step exported HANZO_DEPLOY_TOKEN for any caller that declares a
site, in both roles, so the test role held it while it ran the repo's own
code, and a branch pushed for a pull request could print it. The token is
sealed once per org and publishes every site of that org. The site lane
reads it only in the publish role off a pull request, so the fetch is
there and nowhere else.
uv venv makes a venv without pip, so the clean install before upload
answered "No module named pip" for every wheel and the lane published
nothing (hanzoai/python-sdk v8.5.162, run 36508815355). The install and
import move into alone(), which bin/alone_test.sh lifts out of the
workflow and runs against wheels it builds.
Ubuntu's ldconfig cache lists libclang-18.so.18 and libclang-18.so,
never a bare libclang.so, so the probe missed the copy the runner image
carries and fell through to sudo apt-get, which the runner refuses
("effective uid is not 0"). hanzoai/decision run 36307074308 died there.
clang-sys searches libclang-*.so itself, so no LIBCLANG_PATH is needed.
Claude-Session: https://claude.ai/code/session_018Gt5TvrE2rxXuZikWSauv3
The suite printed only a summary line, and the reusable's test tally,
which counts per-result lines, read that as a runner that ran nothing
and failed main.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
The fake clock rode on bash's ticking SECONDS, so a starved runner
spent 14 real seconds of the 30-minute budget and the suite failed
at 1786. Unset, SECONDS is a plain variable only the fake sleep moves.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
A fixed 30-second poll for 20 minutes was measured short: hanzoai/cd
asked 41 times on 2026-09-23 and got 429 every time, then failed a
publish whose tests had passed. Every waiting repo of an org also hit
the door in the same second a slot freed.
The wait now doubles from 15s to a 120s ceiling with jitter, takes a
numeric Retry-After as a floor, and stops at 30 minutes, since a build
holds its slot for up to ~39. bin/enqueue_test.sh lifts the function
out of build.yml and runs it against a faked wire and clock.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
curl -L keeps every hop's headers, and the check took the last
content-length it saw. A root that redirected once before a
chunked page was measured against the redirect's 17 bytes, which
failed a healthy publish of dev-hanzo-ai three times over.
Claude-Session: https://claude.ai/code/session_01TUxvfuta1Z44KVNYuAz71F
ghcr.io/hanzoai/git-runner is linked to no repository, so this repo's
workflow token cannot list its versions and bin/imgver would publish
9.0.0 over and over. The source label links it on the next push.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
hanzo-inc/cloud's gates name their caches with ${GOCACHE:-...}, so they
built into a directory nothing restored and nothing saved. Restore now
exports the two directories it used, and every later step reads them.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
The publish job held its runner up to 40 minutes polling the registry so
it could write the version tags itself: 289 runner-minutes in 48 hours
spent waiting. The door now writes those tags in the same push and says
so in its answer, so the job ends on the 202 and names the build to
watch. A tag the door does not promise is still written here, and
`wait: 'true'` holds the job for a caller that uses the image next.
The first ref is taken by expansion: `echo | head -1` under pipefail
failed hanzoai/ci's own publish with a broken pipe.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
A runner lives one job, so no Go cache was ever restored: 142 Go-gate
minutes in 48 hours were 31 downloading modules and 69 compiling, and a
warm pass compiled commerce's vet in 15s against 198s cold.
The cache lives in the org's own bucket behind cloud's /v1/s3, reached
with the IAM token the KMS step already holds, so the server keeps one
org out of another's and CI holds no bucket key. Go trusts a restored
cache without re-hashing it, so only the default branch and tags write.
setup-go's own cache is off: it is GitHub's store, and the forge has none.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
Every job downloaded Go and Node through setup-go and setup-node, 110
runner-minutes in 48 hours, because nothing was in the tool cache they
read. Go 1.27.1 and Node 22 now sit there in their layout, beside
corepack's pinned pnpm, helm, crane and zstd.
The image is an images: entry built at the build door on every push.
Its forge lane stopped when Actions went off on the mirror, and a
runner has no docker daemon to run it anyway.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
`secrets: inherit` passes nothing to a reusable workflow in another
org, and a caller may only name a secret the reusable declares. Every
luxfi, zooai, lux-apps, hanzo-inc and hanzo-apps caller therefore
reached the KMS step with KMS_CLIENT_ID empty and delegate refused on
an empty HANZO_API_TOKEN. All five are optional, so a hanzoai caller's
`inherit` is unchanged.
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8