dev is ours: the Hanzo Cloud by default, a binary named dev, our namespaces

hanzoai/dev went public defaulting to someone else's cloud. Four things were
wrong, and all four now have a test that fails if they come back.

DEFAULT PROVIDER AND MODEL. built_in_model_providers() gains `hanzo` -- the
Hanzo Cloud at api.hanzo.ai/v1, Responses wire, bearer from HANZO_USER_KEY --
and it is the default. The default model is zen5-coder. This was not cosmetic:
the old default is not in the gateway catalog, so it 400s against our own
cloud. Measured, not assumed:

  POST api.hanzo.ai/v1/responses model=gpt-5-codex -> 400 "model is not
      available. Use GET /v1/models to list available models"
  POST api.hanzo.ai/v1/responses model=zen5-coder  -> 200, clean output

Our own CLI already documented the bug from the other side -- dev.rs says
"Name the model too -- codex's built-in default is not in the gateway catalog
and would 400" and passes `-c model=` on every launch to work around it. It
also asserts that on the native gateway NO model_provider override is needed,
which is only true once `hanzo` is the built-in default. It is now.

zen5-coder over enso because dev is a coding agent and because enso returns
system-prompt text on the Responses wire that dev actually speaks; zen5-coder
returns the answer. Model ids were read from GET /v1/models with a bearer, not
guessed. Other providers stay fully configurable -- this is the default, not
the only choice.

THE LOGIN COMMAND. Messages said `code login` / `codex login`. The real command
is `hanzo auth login` (`hanzo login` is not a command -- it is swallowed as an
AI task). It now lives once, as auth::LOGIN_COMMAND, so no message can drift
into naming a command that does not exist. References to this binary's own
subcommand became `dev login`.

A BINARY NAMED dev. HEAD built `code` and `codex` and nothing named `dev`, so a
release cut today would ship no `dev` -- and `hanzo code` launches a bare `dev`
off PATH, so every new install would break. `dev` is now the canonical bin,
declared first; `code` remains as an alias to the same program, never the
reverse. The CLI identifies as `dev` / "Hanzo Dev", not "OpenAI Codex".

OUR NAMESPACES. The pipeline published to @just-every/* on npm and pushed
formulae to just-every/homebrew-tap. Both of ours already exist and are already
populated at v0.6.91 -- @hanzo/dev plus all five @hanzo/dev-<target> packages,
and hanzoai/homebrew-tap's Formula/hanzo-dev.rb, which installs `dev` and
expects assets named dev-<triple>. Nothing here is squatted; this is our own
distribution, reclaimed. Assets are renamed to match the formula.

Two live supply-chain bugs fell out of that, both worse than a naming problem:
postinstall.js (runs on every npm install) and the tui auto-updater fetched
from a third party's releases -- the updater also told our users to
`npm install -g @just-every/code@latest`. Worse, a shadowed `isWSL` const put
the two fast paths in the temporal dead zone, so their catch blocks swallowed
the error and EVERY install fell through to that download: the five published
platform packages were never used, and the third-party fetch was not an edge
case but the only path that ran. Fixed and exercised in a sandbox across all
three paths. codex-cli/.pack/ -- a committed `npm pack` artifact CI
regenerates, and the reason the bad URL had two homes -- is now untracked.

DURABILITY, because upstream-merge.yml merges openai/codex every 30 minutes.
A one-time edit to a default is a suggestion that survives until the next
merge. Two mechanisms:

  - .github/merge-policy.json prefer_ours_globs listed only codex-rs/** while
    the workspace that SHIPS is code-rs/**, so it protected nothing that
    ships. The identity-bearing code-rs files are now listed.
  - code-rs/core/tests/hanzo_identity.rs asserts all four properties -- the
    provider, a Zen default, a bin named dev, and no third-party publish
    target. Upstream has no such file, so a merge cannot conflict with it or
    carry the guard away with the thing it guards. Policy can be edited;
    a red build cannot be ignored.

The gate is not decorative: it failed on the un-retargeted pipeline
("release.yml still references `@just-every/`") and went green only when the
pipeline was actually fixed.

Verified: cargo build --bin dev clean; hanzo_identity 4/4; code-core lib
332 passed, 1 failed -- git_info::test_collect_git_info_with_remote, which
fails identically on a clean origin/main checkout here because this machine
has a global [email protected]:.insteadof rewrite. Pre-existing and
environmental, not from this change.

  $ dev exec "say OK"        # fresh profile, no config
  Hanzo Dev v0.0.0
  model: zen5-coder
  provider: hanzo

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-01 14:05:00 -07:00
co-authored by Hanzo Dev
parent d3a9c6aa46
commit d3d1dfa1d3
49 changed files with 660 additions and 2180 deletions
+2 -2
View File
@@ -24,8 +24,8 @@ if [ -z "$package_version" ]; then
exit 1
fi
expected_header="## @just-every/code v${package_version}"
actual_header=$(grep -m1 '^## @just-every/code v' "$notes_file" || true)
expected_header="## @hanzo/dev v${package_version}"
actual_header=$(grep -m1 '^## @hanzo/dev v' "$notes_file" || true)
if [ "$actual_header" != "$expected_header" ]; then
echo "release notes header mismatch" >&2
+6 -6
View File
@@ -18,19 +18,19 @@ fi
echo "[ci-tests] CLI smokes with host binary..."
BIN="${CI_CLI_BIN:-}"
if [[ -z "${BIN}" ]]; then
if [[ -x ./code-rs/target/dev-fast/code ]]; then
BIN=./code-rs/target/dev-fast/code
elif [[ -x ./code-rs/target/debug/code ]]; then
BIN=./code-rs/target/debug/code
if [[ -x ./code-rs/target/dev-fast/dev ]]; then
BIN=./code-rs/target/dev-fast/dev
elif [[ -x ./code-rs/target/debug/dev ]]; then
BIN=./code-rs/target/debug/dev
fi
fi
if [[ -z "${BIN}" || ! -x "${BIN}" ]]; then
echo "[ci-tests] CLI binary not found; building debug binary..."
pushd code-rs >/dev/null
cargo build --bin code -q
cargo build --bin dev -q
popd >/dev/null
BIN=./code-rs/target/debug/code
BIN=./code-rs/target/debug/dev
fi
"${BIN}" --version >/dev/null
+71 -38
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
# Generate a minimal Homebrew formula from the latest GitHub release.
# Writes Formula/Code.rb into the repo root (not a tap); you can copy it
# into a tap repo to publish.
# Generate the Homebrew formula from the latest GitHub release.
# Writes Formula/hanzo-dev.rb into the repo root (not a tap); release.yml copies
# it into hanzoai/homebrew-tap as Formula/hanzo-dev.rb.
owner_repo="just-every/code"
owner_repo="hanzoai/dev"
version="${1:-}"
if [ -z "$version" ] && [ -f "code-rs/Cargo.toml" ]; then
version="$(awk -F '"' '/^\[workspace.package\]/{f=1; next} f && $1 ~ /version/ {print $2; exit}' code-rs/Cargo.toml)"
@@ -21,11 +21,25 @@ fi
# Optional directory where CI placed artifacts (step: Prepare release assets)
RELEASE_ASSETS_DIR=${RELEASE_ASSETS_DIR:-"release-assets"}
# Every platform the tap formula serves. Dropping one silently narrows the
# formula, so all four are required to produce a publishable file.
assets=(
"code-aarch64-apple-darwin.tar.gz"
"code-x86_64-apple-darwin.tar.gz"
"dev-aarch64-apple-darwin.tar.gz"
"dev-x86_64-apple-darwin.tar.gz"
"dev-aarch64-unknown-linux-musl.tar.gz"
"dev-x86_64-unknown-linux-musl.tar.gz"
)
placeholder_for() {
case "$1" in
dev-aarch64-apple-darwin.tar.gz) echo "__SHA_MACOS_ARM__" ;;
dev-x86_64-apple-darwin.tar.gz) echo "__SHA_MACOS_INTEL__" ;;
dev-aarch64-unknown-linux-musl.tar.gz) echo "__SHA_LINUX_ARM__" ;;
dev-x86_64-unknown-linux-musl.tar.gz) echo "__SHA_LINUX_INTEL__" ;;
*) echo "" ;;
esac
}
sha256_file() {
local f="$1"
if command -v shasum >/dev/null 2>&1; then
@@ -51,43 +65,61 @@ retry() {
}
mkdir -p Formula
cat > Formula/Code.rb <<'RUBY'
class Code < Formula
desc "Terminal coding agent"
homepage "https://github.com/just-every/code"
RUBY
formula="Formula/hanzo-dev.rb"
echo " version \"v${version}\"" >> Formula/Code.rb
cat > "$formula" <<'RUBY'
class HanzoDev < Formula
desc "Local AI coding agent for your terminal, powered by Hanzo AI"
homepage "https://github.com/hanzoai/dev"
version "__VERSION__"
license "Apache-2.0"
cat >> Formula/Code.rb <<'RUBY'
on_macos do
if Hardware::CPU.arm?
url "__URL_ARM64__"
sha256 "__SHA_ARM64__"
else
url "__URL_X64__"
sha256 "__SHA_X64__"
on_arm do
url "https://github.com/hanzoai/dev/releases/download/v#{version}/dev-aarch64-apple-darwin.tar.gz"
sha256 "__SHA_MACOS_ARM__"
end
on_intel do
url "https://github.com/hanzoai/dev/releases/download/v#{version}/dev-x86_64-apple-darwin.tar.gz"
sha256 "__SHA_MACOS_INTEL__"
end
end
on_linux do
on_arm do
url "https://github.com/hanzoai/dev/releases/download/v#{version}/dev-aarch64-unknown-linux-musl.tar.gz"
sha256 "__SHA_LINUX_ARM__"
end
on_intel do
url "https://github.com/hanzoai/dev/releases/download/v#{version}/dev-x86_64-unknown-linux-musl.tar.gz"
sha256 "__SHA_LINUX_INTEL__"
end
end
livecheck do
url :stable
strategy :github_latest
end
def install
bin.install Dir["code-*"].first => "code"
# Provide a compatibility shim
(bin/"coder").write <<~EOS
#!/bin/bash
exec "#{bin}/code" "$@"
EOS
# Installs `dev` ONLY. It must not also write a `hanzo` shim: `hanzo` is the
# Hanzo CLI (Formula/hanzo.rb), a different program. A shim here would make
# `hanzo` mean "the dev coding assistant" for anyone who installed this
# formula first, and would collide with hanzo.rb on the same filename.
bin.install Dir["dev-*"].first => "dev"
end
test do
system "#{bin}/code", "--help"
system "#{bin}/dev", "--help"
end
end
RUBY
sed -i.bak "s#__VERSION__#${version}#" "$formula"
for a in "${assets[@]}"; do
url="https://github.com/${owner_repo}/releases/download/v${version}/${a}"
tmp="/tmp/${a}"
tmp="${TMPDIR:-/tmp}/${a}"
sha=""
# Prefer local artifact if available to avoid CDN propagation races
@@ -101,25 +133,26 @@ for a in "${assets[@]}"; do
if [ -z "$sha" ]; then
echo "Downloading ${url} (fallback for sha256)..." >&2
if ! retry 12 5 curl -fsSL "${url}" -o "${tmp}"; then
echo "WARN: Could not download ${url} to compute sha256 (possible CDN delay)." >&2
echo " Proceeding without sha; Homebrew step will still push formula referencing the URL." >&2
echo "ERROR: could not download ${url} to compute sha256." >&2
else
sha=$(sha256_file "$tmp" || true)
fi
fi
# Apply URL (always), and sha when available
if [[ "${a}" == *"aarch64-apple-darwin"* ]]; then
sed -i.bak "s#__URL_ARM64__#${url}#" Formula/Code.rb
if [ -n "$sha" ]; then sed -i.bak "s#__SHA_ARM64__#${sha}#" Formula/Code.rb; fi
else
sed -i.bak "s#__URL_X64__#${url}#" Formula/Code.rb
if [ -n "$sha" ]; then sed -i.bak "s#__SHA_X64__#${sha}#" Formula/Code.rb; fi
# A formula shipped with an unresolved placeholder is a broken tap: `brew
# install` fails on every machine until the next release. Refuse to emit one.
if [ -z "$sha" ]; then
echo "ERROR: no sha256 for ${a}; refusing to write a formula with an unresolved placeholder." >&2
rm -f "$formula" "$formula.bak"
exit 1
fi
ph="$(placeholder_for "$a")"
sed -i.bak "s#${ph}#${sha}#" "$formula"
done
rm -f Formula/Code.rb.bak
echo "Wrote Formula/Code.rb for v${version}" >&2
rm -f "$formula.bak"
echo "Wrote ${formula} for v${version}" >&2
# Optional: best-effort HEAD check to surface propagation status without failing CI
for a in "${assets[@]}"; do
+13 -13
View File
@@ -33,24 +33,24 @@ try {
} catch { Warn ("npm config read failed: " + $_) }
# 2) Resolve versions and tarballs from NPM
Step "Resolve @just-every/code@$Version (npm)"
Step "Resolve @hanzo/dev@$Version (npm)"
$mainVersion = $null
try { $mainVersion = (npm view "@just-every/code@$Version" version) } catch {}
if (-not $mainVersion) { Err "Could not resolve @just-every/code@$Version"; exit 1 }
try { $mainVersion = (npm view "@hanzo/dev@$Version" version) } catch {}
if (-not $mainVersion) { Err "Could not resolve @hanzo/dev@$Version"; exit 1 }
$mainTar = $null
try { $mainTar = (npm view "@just-every/code@$mainVersion" dist.tarball) } catch {}
try { $mainTar = (npm view "@hanzo/dev@$mainVersion" dist.tarball) } catch {}
Info ("main version = " + $mainVersion)
Info ("main tarball = " + ($mainTar ?? "<none>"))
Step "Resolve @just-every/code-win32-x64@$mainVersion (npm)"
Step "Resolve @hanzo/dev-win32-x64@$mainVersion (npm)"
$winVersion = $null; $winTar = $null
try { $winVersion = (npm view "@just-every/code-win32-x64@$mainVersion" version) } catch {}
try { $winVersion = (npm view "@hanzo/dev-win32-x64@$mainVersion" version) } catch {}
if ($winVersion) {
try { $winTar = (npm view "@just-every/code-win32-x64@$mainVersion" dist.tarball) } catch {}
try { $winTar = (npm view "@hanzo/dev-win32-x64@$mainVersion" dist.tarball) } catch {}
Info ("win version = " + $winVersion)
Info ("win tarball = " + ($winTar ?? "<none>"))
} else {
Warn ("@just-every/code-win32-x64@$mainVersion not found on npm")
Warn ("@hanzo/dev-win32-x64@$mainVersion not found on npm")
}
# 3) Download + inspect npm tarballs
@@ -80,7 +80,7 @@ if ($gotMain) {
try {
mkdir p1 | Out-Null
tar -xzf $t1 -C p1
Step ("@just-every/code@" + $mainVersion + " contents")
Step ("@hanzo/dev@" + $mainVersion + " contents")
dir p1/package
Info "\nHead of bin/coder.js:"
Get-Content p1/package/bin/coder.js -TotalCount 30 | ForEach-Object { " " + $_ }
@@ -97,7 +97,7 @@ if ($gotWin) {
try {
mkdir p2 | Out-Null
tar -xzf $t2 -C p2
Step ("@just-every/code-win32-x64@" + $mainVersion + " contents")
Step ("@hanzo/dev-win32-x64@" + $mainVersion + " contents")
dir p2/package/bin
} catch {
Warn ("Could not extract or inspect win tarball: " + $_)
@@ -106,7 +106,7 @@ if ($gotWin) {
# 4) Compute target triple and cache paths
$triple = "x86_64-pc-windows-msvc.exe" # Windows x64
$cacheDir = Join-Path $env:LOCALAPPDATA ("just-every\code\" + $mainVersion)
$cacheDir = Join-Path $env:LOCALAPPDATA ("hanzoai\dev\" + $mainVersion)
$expectOK = Join-Path $cacheDir ("code-" + $triple) # correct
$expectBAD = $expectOK + ".exe" # incorrect (double .exe)
Step "Cache expectations"
@@ -117,7 +117,7 @@ Info ("exists OK? " + ([string](Test-Path $expectOK)))
Info ("exists BAD? " + ([string](Test-Path $expectBAD)))
# 5) GitHub release asset (fallback path)
$ghZip = "https://github.com/just-every/code/releases/download/v$mainVersion/code-$triple.zip"
$ghZip = "https://github.com/hanzoai/dev/releases/download/v$mainVersion/dev-$triple.zip"
Step "GitHub asset (fallback) HEAD check"
try {
$resp = Invoke-WebRequest -UseBasicParsing -Uri $ghZip -Method Head
@@ -160,7 +160,7 @@ try {
Step "Global npm prefix + expected node_modules bin path"
$prefix = $null
try { $prefix = (npm prefix -g 2>$null) } catch {}
$nmBin = if ($prefix) { Join-Path $prefix "node_modules\@just-every\code\bin" } else { "<unknown>" }
$nmBin = if ($prefix) { Join-Path $prefix "node_modules\@hanzo\dev\bin" } else { "<unknown>" }
Info ("prefix = " + ($prefix ?? "<none>"))
Info ("bin dir= " + $nmBin)
if ($prefix -and (Test-Path $nmBin)) { dir $nmBin } else { Info "(bin dir not present - expected for npx, present for global installs)" }