zap-proto is published and is a runtime for generated code; the generator
needs the zap schema compiler, which this repo does not build with.
Co-authored-by: Hanzo Dev <[email protected]>
protocol carries the events and the ten actions, and every action carries
the id its dispatch and completion are recorded under. encode and decode
are the only two functions that know the byte format, so ZAP takes over
from serde_json without moving a type or a symbol.
core is a Session with step, snapshot and restore, and no effects at all:
no filesystem, no process, no socket, no runtime. codex-core is not
separable as it stands — tokio, an HTTP client, a rollout store, a pty —
so the model-request and tool-result loop is driven here, and the module
doc names what stays coupled upstream. An observation is accepted only
while its id is outstanding, so a replayed result runs nothing twice.
ffi is the C ABI: seven symbols, generational handles so a stale one is
refused rather than resolving to the next session, catch_unwind on every
entry point so a panic poisons one session instead of aborting the
process, and dev.h written beside it. A C program exercises the archive.
protocol and core build for wasm32-wasip1; make wasm is the check.
Co-authored-by: Hanzo Dev <[email protected]>
typescript 7.0.2 is the native compiler and exports no JavaScript API, so
ts-jest, tsup's dts build and typescript-eslint cannot read it. It comes in
aliased as tsgo and does the typechecking through new typecheck scripts;
typescript stays on the 6 line for the tools.
shell-tool-mcp asked for moduleResolution "Node", which TS7 removed, so it
moves to node16 alongside its module; the node and jest globals it uncovers
are now declared rather than swept in. 30 diagnostics before, 29 after, the
same on both compilers.
Co-authored-by: Hanzo Dev <[email protected]>
Release and bump commits were authored by the automation address, so the work never reached the owner's contribution graph.
Co-authored-by: Hanzo Dev <[email protected]>
Release / darwin-amd64 (push) Blocked by required conditions
Release / darwin-arm64 (push) Blocked by required conditions
Release / linux-amd64 (push) Blocked by required conditions
Release / linux-arm64 (push) Blocked by required conditions
Release / windows-amd64 (push) Blocked by required conditions
Release / publish (push) Blocked by required conditions
hanzo-build-linux-amd64, lux-build-linux-amd64, zoo-build-linux-amd64 and
every other org-prefixed spelling all meant the same machine. Which org a
job belongs to does not change which host can build it, so the org bought
one pool name per org per arch and each could go stale on its own -- the
evo host alone answered to fourteen names for one label.
One vocabulary now: linux-amd64, linux-arm64, macos-arm64, win-amd64. The
generic labels every plain workflow uses (ubuntu-latest, self-hosted) are
untouched, so naive CI keeps working.
Co-authored-by: Hanzo Dev <[email protected]>
The first cut died at `curl` exit 22 with nothing about the cause: `set -e`
took the run down before the message meant to explain it, and the step also
reached for jq in the one job that provisions no toolchain.
Now each failure names itself — an empty client id, the HTTP code KMS answered
(body redacted), a login with no accessToken, or no readable token on any path —
and the JSON is read with sed, which is present before anything is installed.
Co-authored-by: Hanzo Dev <[email protected]>
The forge org carries KMS_CLIENT_ID and KMS_CLIENT_SECRET and nothing else, so
secrets.GH_PAT expanded to empty and every call left as
Authorization: Bearer
GitHub answered 401, the draft release was never opened, and all five platform
jobs and publish skipped behind it. Nothing has shipped since v0.6.106 on
September 2 — a release train stopped by a header with nothing after the space.
The publish job already reads NPM_TOKEN from KMS; the GitHub token now arrives
by the same road, in the three jobs that need it, and a token KMS will not hand
over fails the run loudly instead of expanding to nothing.
Co-authored-by: Hanzo Dev <[email protected]>
logout cleared the saved Hanzo key whenever the file existed, so signing out
of ChatGPT deleted a Hanzo key the user pasted weeks ago. The two credentials
are separate accounts; destroying the one nobody asked about is a surprise
nothing undoes. The gate caught it:
assertion failed: home.path().join("hanzo-api-key").exists()
logout_removes_only_the_selected_providers_saved_key
The active provider now decides which credential goes. The Hanzo side still
runs FIRST, because run_logout exits the process and anything sequenced after
it is unreachable — the defect that once printed success while leaving the
credential readable.
Co-authored-by: Hanzo Dev <[email protected]>
hanzoai/ci moved the reusable to .github/workflows/build.yml — the one path
both planes resolve, since github.com reads a reusable only from there and the
forge reads it too. The v1 tag moved with it, so every caller still naming
.hanzo/workflows/build.yml asks for a blob that is not in the tree:
PrepareRunAndInsert: InsertRun: read hanzoai/ci@v1:.hanzo/workflows/build.yml:
object does not exist
Ninety-five repos still name the old address; this is one of them. The caller
stays at .hanzo/workflows/cicd.yml, which is deliberate — github.com has no
runner for these labels, so a caller placed there could never be scheduled.
Co-authored-by: Hanzo Dev <[email protected]>
Four surfaces still printed the upstream product. Found by running every
subcommand and reading the output rather than by grepping the sources, which
is why they survived the earlier sweep: three live in help text that only
clap assembles, and one is a JSON key.
--profile $CODEX_HOME/<name>.config.toml
--ignore-user-config $CODEX_HOME/config.toml
doctor "rerun codex doctor", "CODEX_HOME was resolved"
doctor --json "codexVersion"
sandbox the sandbox-state capability name
$CODEX_HOME was not only off-brand, it was wrong: initialize reads DEV_HOME and
writes CODEX_HOME, so a reader who followed the help and set CODEX_HOME had it
overwritten. The name that is ours is also the one that works.
doctor --json is a machine surface, so its key is a contract; both report
shapes carry it and both are renamed. The sandbox capability is a wire
identifier and keeps its value — only the sentence describing it changes.
Co-authored-by: Hanzo Dev <[email protected]>
`dev login` opened a browser for ChatGPT and had no way to reach the account
the tool actually runs on. It now offers the four credentials that exist —
Hanzo account, ChatGPT account, or a pasted key for either — with Hanzo first
and selected, because Hanzo is the provider the default config names.
A pasted key lands in the profile's own file, written through a temporary file
so a failed write cannot truncate a working credential, and readable only by
its owner. It is separate from auth.json, which the Hanzo tools share.
Logout was the defect worth the most here: `run_logout` exits the process, so
every statement after it was unreachable. On the default provider the Hanzo
credential was never cleared at all, and the command still printed success —
a logout that left the account readable. The Hanzo side is cleared first now,
on every path, and a failure on one side no longer stops the other.
Activating the Hanzo provider also writes the provider table when the profile
lacks it, so switching to Hanzo in an older profile no longer names a provider
that is not defined.
Co-authored-by: Hanzo Dev <[email protected]>
Adding `submodules: true` to the checkout step left two jobs with two
`with:` blocks. GitHub's workflow parser rejects a duplicate mapping key
outright, so the release lane could not be scheduled at all. It passed
review here because `yaml.safe_load` keeps the last duplicate silently —
the check now refuses duplicates the way GitHub does, and runs over all
three workflow files.
The --strict-config anchor started mid-clause, so the help it produced
read "fields that are this version of Dev does not recognize". It takes
the whole clause now. The branding test did not catch this because it
greps for the upstream name and never for coherence.
The branding check read `dev --help` only, which is why `dev exec`
announced "OpenAI Codex" as its banner for a while — `--help` never shows
it. It now reads every subcommand's help, and found four more leaks by
itself: exec's usage line, its --strict-config and --skip-git-repo-check
text, and `-c/--config` still naming ~/.codex/config.toml. That last one
was recorded as unreachable when a copied island could not include
utils/cli; editing the checkout in place made it reachable.
Two things keep the word legitimately and are exempt: CODEX_HOME, an
environment variable upstream owns, and `codex/...`, which names a method
on the app-server wire protocol. Renaming either breaks something real.
Branding is the product name a reader sees, not identifiers on a wire.
hanzo-upstream had no tests, and it is the code that edits upstream in
place — including an idempotence bug shipped earlier today that stacked
appending edits until `hanzo-tui` appeared three times in one manifest.
The anchor logic is now a pure function with six tests, one of which is
that exact regression, and one of which asserts every shipped anchor is
well formed. `make test` reaches the crate now; being a detached
workspace, the root run could not see it.
Cloud tasks are Hanzo Tasks, not the upstream service's name.
`codex-cli/` published `@hanzo/dev`, so the directory carrying our npm
package was named after someone else's product. It is `npm/`.
`patches/codex.json` is `patches/upstream.json` — it anchors edits against
upstream, and the file should say so rather than name one of them.
Deleted outright: `justfile`, whose first line was
`set working-directory := "codex-rs"` for a directory this repository has
never had; `announcement_tip.toml`, an example file for a TUI we do not
ship; and `.codex/` and `.code/`, which held upstream's own process skills
— babysit-pr, codex-bug, pushing-ci-changes — that were never ours.
Three branding leaks a real run turned up, none of which the help text
showed: `dev exec` announced "OpenAI Codex v0.7.0-dev.1" as its first
line, the status card was missed when the session header was anchored,
and both bubblewrap warnings said "Codex's Linux sandbox".
README: say what ships. It claimed the agent drives a browser, which is
the port that has not landed, and showed a screenshot of it doing so.
Both are gone until the bridge is back. The model count is "hundreds" —
`/v1/models` answers 458 today, an earlier line here said "over 500", and
hanzo.ai derives its own count precisely because typed ones drift.
Document the npm install beside the curl one; both give the same build.
The product home was `~/.hanzo/dev2` — named after the migration workspace,
which is now an archived repository. It is `~/.hanzo/dev`.
`~/.hanzo/auth.json` is the credential. Dev links its own auth.json to it,
so signing in to any Hanzo tool signs in to all of them while dev keeps its
own sessions and settings. A credential the user already has is left alone
rather than replaced by a link, running it twice changes nothing, and an
explicit DEV_HOME is a separate profile with its own account — each of
those is a test, because each is a way to lose someone's login.
LLM.md still described code-rs, build-fast.sh and a VT100 harness under a
directory this repo no longer has — and AGENTS.md, CLAUDE.md and GEMINI.md
all symlink to it, so it is the file every agent reads first. Rewrite it
for the workspace that is actually here, including the rule that upstream
is never edited by hand because `make bump` discards such edits.
Fix five broken images: the README pointed at docs/logo.png and
docs/screenshots/, and they live in docs/images/. Predates the cutover.
State the catalog honestly. "enso and the zen5 family" undersold a gateway
that answers for over five hundred models. Round, not exact — hanzo.ai
derives its count from the pricing snapshot and fails the build on a
hand-typed one, and a number typed here would drift the same way.
Replaces the Rust tree. `code-rs/` was a fork of just-every/code carrying
its own copy of everything; `vendor/` held 219 MB of upstream snapshots
beside it. Both are gone.
In their place: Hanzo writes the command surface, upstream keeps the
engines. `crates/hanzo-dev/src/main.rs` is a real file that dispatches
into libraries — codex_tui::run_main, codex_exec::run_main, and the rest
— rather than an include! of upstream's 5156-line binary main. Owning the
front door is what makes a single unified command surface expressible.
Upstream arrives as submodules at upstream/codex and upstream/code, both
at latest. The gitlink is the pin, so no lockfile of hashes sits beside
it and a bump is a two-line diff instead of forty-five thousand. Crates
are consumed by ordinary path dependency; nothing is copied.
The whole Hanzo delta on upstream is sixteen anchored substitutions in
patches/codex.json, applied in place by `make prepare` and visible in one
`git -C upstream/codex status`. Each carries an exact match count, so
upstream moving the ground under one stops preparation and names the file
rather than branding the wrong line. Two are upstreamable: they publish
mcp_cmd, plugin_cmd and doctor from upstream's library instead of burying
them in its binary.
`dev` covers exec, login, logout, serve, resume, fork, review, agents,
queue, archive, unarchive, delete, cloud, mcp, plugin, doctor, features,
sandbox and completion. Auto Drive and the browser bridge are not ported
yet; migration/STATUS.md records that, and the defects the v1 patch was
already carrying, so they are fixed as features are ported rather than
copied across.
Releases build from the root after `make prepare`, with submodules
checked out. Scripts that existed only to manage a fork-diff — the
upstream-merge set and the codex path-dep check — go with the fork.
Mirror openai/codex c308ffb36 exactly under vendor/codex. Apply the code-rs delta from just-every/code 7d59424890 to af789f51a, retaining Hanzo defaults, authentication, branding, and vendor paths. Give the compatibility code binary its own entry file to eliminate duplicate-target warnings.
Validated: BUILD_FAST_BINS="dev code" ./build-fast.sh, both binary versions, shell completion, and interactive Hanzo TUI startup.
Ranges drift once nothing moves them: a caret that has not been rewritten
since it was written pins whatever was current that day, and an exact pin
cannot even take a patch. Both are updated here.
One line per package, so a surface reads the same components, the same
tokens and the same face as every other one.
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.
Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.
Comments naming a face we no longer use are deleted, not reworded.
A version written into a manifest is written into every manifest that wants it,
which is how eslint came to exist in a hundred and four forms across this estate
and how a package came to resolve twice at run time. The catalog moves the number
out of the manifest: a manifest names the package, the workspace names the
version.
Four are held below their latest because the newer release cannot be used, and
the reason is recorded rather than the version alone: @noble/hashes 2 renamed
every subpath the web3 stack imports, @splinetool/runtime 2 depends on a package
that was never published, @dynamic-labs 5.4.1 the same, and react-resizable-panels
3 points its types condition at the bundle.
runs-on was a bare scalar. Jobs written that way queue and are never
picked up — link and quest have waited since 10 August, painter since the
5th, none of them with a single success — while repos writing the same
label as a sequence drain within seconds of a push on the same fleet.
Same label, same runners, different shape. It fails silently: nothing errors,
the job simply never starts, so the lane looks configured and does nothing.
Every dependency on a @hanzo/@luxfi/@zooai package now names the same version
across the repo: a catalog entry where pnpm supports one, an exact pin where it
does not, and a range only where a peer genuinely means one.
pnpm itself is pinned too — the estate ran eighteen different versions, five of
them too old to read a catalog.
Pydantic omits `required` entirely when every argument is optional,
which is how FastMCP writes most of a Python server's tools. Our Object
serializer read an absent list as "every property is required", so the
model was told `agent` needs all fifteen of its arguments and `mpc` all
seventeen — 17 of the Hanzo server's 95 tools, measured by running their
live schemas through the converter. JSON Schema reads an absent list as
requiring nothing, so write that list where a server omits one.
While in the same resolver: measure a scalar `type` against the same six
types the union path already measures against, rather than against
`!= "null"`. The list is named once and read by both, so they cannot
disagree about what we can hold, and a type outside it degrades to an
accept-all string instead of failing the whole tool it sits in.
The version job picks the next patch above the newest tag, the newest npm
package and package.json. All three of those appear only when a run
publishes, about twenty-five minutes after it starts. A second run that
begins inside that window sees the old numbers and picks the same version,
cross-builds five targets, and dies on `npm publish`:
npm error You cannot publish over the previously published versions: 0.6.101.
That is exactly what happened to v0.6.102. The draft release the first run
opened at second zero was already the reservation; nothing read it back.
Now it counts, so the window closes to the moment between reading and
opening a draft rather than the length of a whole build.
`concurrency: release` does not serialize these runs — two were in flight
at once, which is how the overlap arose in the first place.
Co-authored-by: Hanzo Dev <[email protected]>
postinstall downloads a binary from a release and executes it. Until now
the only check on those bytes was the file magic — proof that they are an
executable, not proof that they are ours. Every release asset already
ships a sibling .sha256; read it and compare.
The per-user cache is keyed by version number alone, so a binary of
unknown provenance sitting under an already-cached version survived every
reinstall. A cache entry is now trusted only while a stamp beside it
still matches its bytes, and the stamp is written after the published
checksum verified the archive it came from — so a cache hit carries that
proof forward.
Verified against v0.6.100: a tampered cached binary is refused,
re-downloaded, checked and repaired.
Co-authored-by: Hanzo Dev <[email protected]>
doctor is where a user with three installs goes to find out which to
remove, and it was telling them `brew uninstall code` — a formula that
was never published — and to "prefer 'coder'", a name that is not the
canonical one. It also announced itself as "code version".
The guidance now lives in one value so a test can hold it, the way
hanzo_identity.rs holds the defaults. Prose drifts; a value with a test
does not.
Co-authored-by: Hanzo Dev <[email protected]>
The first pass took the comments. This takes what is left: error strings a
person meets when a build fails, where a word naming no system is worst of all.
Hanzo Git is the product, git.hanzo.ai the address. ("unforgeable" stays.)
FORGE_TOKEN was a hand-provisioned PAT kept as an org-wide Actions secret. Hanzo
Git hands every job a token — checkout writes it into http.extraheader with no
secret involved — so this was a second credential home beside KMS that rotted on
its own schedule and bought nothing.
Now GIT_TOKEN, from github.token.
"Forge" named nothing in this estate. The product is Hanzo Git, the address is
git.hanzo.ai, and the in-cluster service is hanzo-git.hanzo.svc. A generic word
invented for it sends a reader looking for a system that does not exist.
Comments only. ("unforgeable" stays — different word.)
The asset check proved names and checksums; nothing proved the bytes
were programs. The linux binary is native to the publish runner, so it
speaks for the matrix: it must print the release's own version and emit
completions before the draft becomes real.
Co-authored-by: Hanzo Dev <[email protected]>
XWIN_VERSION is cargo-xwin's --xwin-version (the Windows SDK manifest);
a crate version in it kills every msvc build with 'invalid digit found
in string'. The pin is renamed to what it is.
Co-authored-by: Hanzo Dev <[email protected]>
The lane moves off the host that froze under every full build and onto
the pool that already ships hanzoai/cli's five platforms the same way:
darwin cross-linked by zig against a vendored, checksum-pinned MacOSX
SDK, musl natively, windows-msvc through cargo-xwin with LLVM's
clang-cl — five parallel jobs, each uploading its assets into a draft
release, and a publish job that refuses anything short of all 28 before
the release becomes real. The push trigger returns with it: releasing
on merge no longer crashes anyone's machine, because no machine of ours
is special anymore.
Co-authored-by: Hanzo Dev <[email protected]>
Five freezes in 24 hours, each under this workflow's compile load — at
16 jobs, at 8, with and without the host's rustc wrapper. That is the
machine, not the lane. A push trigger turns every merge into another
crash of the host; dispatch keeps the lane one command away while the
Mac gets diagnosed.
Co-authored-by: Hanzo Dev <[email protected]>
A vendor key exported in the shell picked the provider implicitly, and
the onboarding screen took that as identity: it greeted a fresh user
with "Sign in with ChatGPT" and offered their ANTHROPIC_API_KEY back to
them. An inferred provider is not a decision. Sign-in now leads with
Hanzo IAM unless a provider was NAMED (flag, profile or config.toml),
a completed Hanzo sign-in flips the session onto the account it just
created, and a stored Hanzo session outranks a stray vendor key in the
next launch's implicit pick.
The sign-in waiting screens also stop shimmering: each render scheduled
another frame in 100ms, redrawing the whole terminal ten times a second
for as long as the login took — the "spazzing". A waiting screen is now
a still line, and the shimmer module goes with its last caller.
Co-authored-by: Hanzo Dev <[email protected]>
Three runs, three host deaths, each mid-compile of the final crates.
A release that reliably kills its own runner never finishes; capped
parallelism costs minutes and keeps the machine alive.
Co-authored-by: Hanzo Dev <[email protected]>