"Cuts and ships a uzi release for this repo (hosted on GitHub), and nothing else: confirms nothing wanted in the release is still unmerged and main's CI is green, then cuts the release (RC-first version bump, CHANGELOG, tag, GHCR publish, deploy verify) INLINE as the lead with the bundled release-cut/release-watch/release-verify scripts and presents the summary while it publishes in the background. Reviewing and merging open PRs, Renovate included, is the uzi-lander skill. Use when the user runs /uzi-release, or asks to cut or ship a uzi release, promote an RC, bump the uzi version, or deploy uzi. Triggers include uzi-release, release uzi, cut a uzi version, cut an RC, promote the RC, ship uzi, deploy uzi."
Recursos
1Instal·la
npx skillscat add vtmocanu/uzi/uzi-release Instal·la des del registre de SkillsCat.
uzi-release
Cut a uzi release, watch it publish, verify it, then summarize. This repo is hosted on GitHub: use gh (which infers the repo from the checkout, so a fork under any owner works unedited; never glab/tea). CI is GitHub Actions (.github/workflows/), images and the Helm chart publish to GHCR on a v* tag.
Boundary. Reviewing and merging PRs, one or the whole open set, Renovate included, is the uzi-lander skill (references/batch.md for the set, references/renovate.md for dependency PRs). This skill starts once what should ship is on main.
Do NOT bake step details that live in the repo into your head. Read the source of truth each run: .github/workflows/release.yml (and ci.yml, kind-smoke.yml, brew.yml), .claude/agents/release.md (the mechanics reference), the destructive-ops rules in CLAUDE.md, and deploy/README.md for deploy (still GitLab-worded in places, not the authority for publish mechanics).
Note on the deploy layer. Cluster/deploy verification needs cluster access you may not have; if so, say so plainly rather than guessing.
0. Guardrails first
- NEVER
docker compose -p uzi down -vand never globuzi-containers (seeCLAUDE.md"Destructive operations"). This skill touches git,gh, and (if you have deploy access)kubectlonly. - Work on
mainin the repo root worktree;release-cut.shmakes its own throwaway worktree for a promote. Never check the root worktree out onto another branch. - The release commit goes direct to
mainunder the admin role's ruleset bypass (uzi-landerreferences/merge-mechanics.mdhas the ruleset). If a push is refused, read the refusal and stop rather than forcing.
1. Preconditions: nothing pending, main green
- Nothing wanted in this release is still open.
gh pr list --state open,uzi run listfor in-flight runs. Anything the user wants in the release gets landed first withuzi-lander(the whole set: itsreferences/batch.md), then come back. Say which open PRs you are leaving out. When unsure what the user wants in, ask. - Exact
maintip green. Fetch and fast-forwardmain. Watch all existing tip workflows to green with.agents/skills/uzi-lander/scripts/watch-run-ci.sh --sha "$(git rev-parse origin/main)" --branch mainin the background; require successfulci.ymlandkind-smoke.ymlpush runs on that exact SHA. Runpython3 -I scripts/release-ci-coverage.py tip HEAD --wait 0for the shared policy. A missing, queued, running, failed or cancelled run blocks the cut. Wait for the exact tip's ci.yml + KinD success BEFORE release-cut: pushing the release commit supersedes main CI and cancels a running parent, destroying inheritance evidence.release-cut.shrepeats this precondition for verbs that write main. For--promote-only, validate candidate coverage instead; the current main tip need not be green. Candidate validation accepts at most two strictly metadata hops to green main-push CI and smoke. Do not substitute evidence from an older commit.
2. Cut the release inline with the deterministic scripts — do NOT dispatch a subagent
Once step 1 holds (nothing pending, main green on the tip), cut the release inline as the lead, driving three bundled scripts. Do NOT dispatch the release subagent for this: a background subagent spawns a watch and then idles, so every wait becomes a manual re-ping (the "agent keeps dying" ping-pong), while the steps that actually need judgment — flake-vs-defect, whether to rerun, authoring the CHANGELOG — are the lead's regardless. .claude/agents/release.md stays the MECHANICS reference (what each step proves, the signing traps, deploy verify), but the driver is the lead + scripts, not that agent.
What the lead decides first (from what merged since the previous stable tag):
- The version
X.Y.Z. Patch (0.46.0→0.46.1) for a maintenance/deps/tooling batch with no user-facing feature or behavior change; minor (0.46.0→0.47.0) when a product feature ships. Patch tags are in this repo's history (v0.42.1,v0.38.1), so a small batch does not force a minor. Model B: chartversion==appVersion== the tag. - RC-first (PRD #1265): which verb. A release is cut as a CANDIDATE
vX.Y.Z-rc.1by default and PROMOTED to stable later, from the candidate's own commit, in lockstep with the next candidate. When an RC is already in flight for a lower base,release-cut.shREFUSES with the facts (which RC, since when, whether[Unreleased]is empty) and exits 3. That refusal is the lead's cue to ask the user which of four: promote the in-flight RC and cut the next candidate (--promote, the usual answer once a candidate has been dogfooded), promote it alone (release-cut B --promote-only, whenmaincarries work the user does not want in a candidate yet), cut another candidate for the same base (release-cut B, when the candidate was not good enough; it folds[Unreleased]into the open## [B]section per###subsection and refuses, CHANGELOG untouched, on text outside a subsection, a non-Keep-a-Changelog subsection name, a duplicate subsection in the result, or a line lost, invented or moved; a###repeated inside[Unreleased]is collapsed with a NOTE), or abandon it (--skip-promote).--stable(the old one-step model) is only for a docs-only release and is refused while an RC is in flight.- RC dogfood install (PRD #1378):
brew install vtmocanu/tap/uzi-cli-rcbuilds the newest candidate from source; itconflicts_withthe stableuzi-cliby design (one activeuzi), so uninstall stable first if present. Switch back withbrew uninstall uzi-cli-rc && brew install uzi-cli. Stable users onuzi-clinever see a candidate. - Stale in-flight RC, and the stable must contain current
main(not the old candidate). When the in-flight RC is many merges behindmainand you want the stable to ship everything onmain: you cannot--stable(refused while an RC is in flight), and a plain--promotetags the OLD rc commit, so neither gives you the tip. The route is rc-at-tip, PUBLISH it, then promote: (1) author the oracle-flagged shipping merges into the open## [B]section and COMMIT them first (the oracle reads the changelog at the committed ref, andrelease-cutrefuses a dirty tree); (2)release-cut.sh BcutsvB-rc.(N+1)at the tip; pushmain; tag immediately once the metadata-only release diff is reviewed; (3) tag that RC and PUSH it (git tag -a vB-rc.(N+1) HEAD, then! git push origin vB-rc.(N+1)), and wait for it to publish (release-watch.shthenrelease-verify.sh). Do NOT keep it local-only: the rc cut'sworker-tag-autobumphas already pinnedworkers.image.tagto that RC, so an unpushed tag would leave the stable chart pointing at anagent-*imagerelease.ymlnever built (--promoteREFUSES an in-flight RC that is not on origin). The redundant RC publish is the price of the invariant. (4)release-cut.sh <B+1> --promotesees zero delta since the new RC and takes the promote-only branch: it tagsvBfrom the tip and cuts NO next candidate. Before promotion, require green candidate coverage, directly or through at most two strictly classified metadata hops. PushvB, watch, verify. Wart: promote-only after an on-main RC cut leavesmain'sChart.yamlatB-rc.(N+1)(behind the stable). Harmless (nothing publishes offmainuntil the next tag; ArgoCD tracks the newest published chart =vB), and the next feature cut clears it. - The stable must be the candidate exactly, and
mainhas moved on.release-cut.sh B --promote-only(B = the in-flight base, not a next version): it tagsvBfrom the RC commit and stops. No next candidate, no commit onmain, nomainpush; work merged after the RC is not invBand ships with the next plainrelease-cut <next>, whose coverage window starts atvB. It keeps--promote's published-RC refusal and takes no--changelog-file/--no-commit/--prev-tag. PushvBonly, watch, verify once. Same wart as above, plusmain's## [B]date stays at the RC-cut date until the next cut of a new base syncs it from thevBtag. Prefer--promotewhen the next candidate is wanted anyway: a plain--promotewith shipping work onmainand an empty[Unreleased]fails the RC half and rolls the stable tag back.
- RC dogfood install (PRD #1378):
- A draft
## [X.Y.Z] - <date>CHANGELOG section (write it to a file forrelease-cut.sh --changelog-file), folded from[Unreleased](keep an empty[Unreleased]on top), Keep-a-Changelog subsections (### Added/### Changed/### Fixed), no em dashes (user preference for user-facing content). The section is keyed by the STABLE baseX.Y.Zeven for a candidate (it is opened atrc.1and reused by every laterrc.Nand the promotion, PRD #1265 D3). Every shipping merge since the previous STABLEv*tag MUST be cited by issue/PR number or short SHA (withghauthenticated, an issue the squash-merged PR closes also counts) — the oraclescripts/assert-changelog-covers-release.shenforces it (shipping =api/**,agent/src/**,controller/**,web/src/**,deploy/chart/**,docs/**, excluding tests;docs(...)-typed,chore(release):, and non-shipping-only merges are exempt).release-cut.shcites the dependency bumpscheck-changelog-entry.shspares (scripts/lib/dependency-bump.sh) in oneRoutine dependency updatesbullet under### Changed; cite everything else. Enumerate them withgit log --first-parent --oneline <prev-stable-tag>..HEAD. (If[Unreleased]already carries the entries, omit--changelog-fileandrelease-cut.shfolds it in.)
The three scripts (.agents/skills/uzi-release/scripts/), in order:
release-cut.sh <X.Y.Z> [--promote|--promote-only|--skip-promote|--stable] [--changelog-file FILE]— the whole prep, deterministic. It reads the LOCALmainand refuses one that is behindorigin/main: fast-forward first. To rehearse a verb you have not run live, clone the repo to a scratch dir,git remote set-url origin <the GitHub URL>,git checkout -B main <commit>, and cut there: every tag stays in the clone. A<commit>behind the liveorigin/maintrips the behind refusal, so to rehearse a past state pointoriginat a scratchgit clone --bareof that state instead. It DERIVES the coverage base (the previous stable tag) itself, so there is no<prev-tag>positional (--prev-tag TAGoverrides for a repair). Default cutsvX.Y.Z-rc.1(folds[Unreleased]), the nextrc.(N+1)for the in-flight base (folds[Unreleased]into the open section, guarded), or refuses (exit 3, with the facts) when a lower RC is in flight.--promotecreates the stablevBtag LOCALLY from a throwawayrelease/Bworktree at the RC commit (metadata-only edits, a diff allowlist checked BEFORE any tag exists, worktree+branch removed after), then cuts the nextvX.Y.Z-rc.1on main.--promote-onlyruns that promote half alone and exits:X.Y.Z= the in-flight base, nothing is committed onmain, and the rest of this step does not apply (reviewgit show vB, push the tag, go to script 3). Every other verb bumpsChart.yaml, runsworker-tag-autobump.sh(rolls the fleet IFF the agent runtime surface changed — the pin may legitimately STAY at a PUBLISHED RC tag inside a stable chart, D11, do not hand-normalize it; an unpublished pin is refused by the release guards),changelog-links.sh, commitschore(release): <tag>on main, and verifies with the oracle +changelog-links --check. On a verify failure it undoes the commit (reset --soft, edits kept staged) and exits 1. It does NOT push and does NOT tag the RC. Reviewgit show HEAD, thengit push origin main.- Review the release diff, push
main, then tag the candidate immediately withgit tag -a vX.Y.Z-rc.1 -m vX.Y.Z-rc.1 HEAD(use the exact tag printed by release-cut), and push it under the user's existing authorization or hand them! git push origin vX.Y.Z-rc.1. Do not wait for the release commit's own full CI.release.ymlenforces successful main-push CI and smoke on the tagged SHA or through at most two strict metadata hops; a known failure on the tagged SHA blocks publication. It validates the tagged chart and bundled changelog, and requires the real tagged KinD smoke before chart publication. Full main CI still runs on the release commit. Never use a skip-ci marker, which suppresses tag workflows too. Promote push order: stable tag, main, candidate tag. A promote-only cut pushes just the stable tag; candidate coverage is required but current-main coverage is not. release-watch.sh <tag>, in the BACKGROUND — it watchesrelease.ymlANDbrew.ymlfor every tag (PRD #1378: an RC publishes the opt-inuzi-cli-rcformula, a stable publishesuzi-cli). It AUTO-RERUNS a transient publish failure (any non-gate job) up to--max-reruns(default 2); gate failures (assert-*/prep) are NOT auto-rerun. For a KinD flake causing redassert-tag-smoke, first rungh run rerun <kind-run> --failedon the failed tag KinD run and wait for success, then rungh run rerun <release-run> --failedon release.yml. Thenrelease-verify.sh <tag>proves the publish: five image tags + chart on GHCR, the cosign "Pushing signature to:" lines, and the channel — a stable Release is marked latest; an RC Release is flagged pre-release withreleases/latestunchanged (the previous stable). On a promote, run verify TWICE: once for the stable tag, once for the new RC. After a stable, also confirm what verify does not:gh release listshowsvBas Latest with novB-rc.*entry left (release.ymlprunes them), the deployment's chart revision reachesB(it lags a reconcile or two even after a hard refresh; re-check before calling it stuck), and an unchanged worker pin rolled no workers.
Manual hotfix compatibility. An off-main stable patch has no main-push CI. Follow deploy/README.md's Hotfix procedure: run affected local gates and check:release-metadata, then annotate the patch tag with the exact standalone line CI-Coverage: hotfix-uncovered. The exception requires the immediately previous stable patch on the first-parent chain and rejects a tag targeting main. In general, a failed main-push run on the tagged SHA or any visited coverage-chain commit vetoes publication. Report UNVERIFIED hotfix, no full CI evidence; tagged metadata, worker-image checks and real tagged smoke still apply. Never present the exception as CI-covered.
Run every watcher in the BACKGROUND, never a foreground blocking gh run watch. A foreground blocking watch pins the turn for the 5-10 min CI/publish window and locks the user out of the session the whole time; that background-vs-foreground split is the one real reason watches must not block the lead. Use a run_in_background command (or a background waiter) and react to its completion notification.
A tag push needs the user's go-ahead. It publishes unattended, so without an explicit in-session authorization the harness classifier blocks git push origin v* (Blocked by classifier, from a subagent and the lead alike); with one, the lead pushes the tag itself. That is a harness permission-mode block, distinct from the protect-release-tags ruleset (which would instead come back as a git/remote rejection). If blocked, the annotated tag already exists locally (shared across worktrees via .git), so hand the user one command: ! git push origin vX.Y.Z. The admin role then bypasses the ruleset's creation restriction (remote: Bypassed rule violations … [new tag]). Do NOT route around it with gh api to create the ref — that defeats the same intent.
Present the step-3 summary once the tag is pushed — the publish runs unattended in the background (release-watch.sh) while you report what shipped, and you fold the verified tags (or a block) into the summary when release-verify.sh confirms.
3. Final summary (always end with this)
Report to the user, concisely:
- One line up top: version X.Y.Z and its status — cut / CI-green / tag pushed / published to GHCR (images + chart) / blocked on
<what>(e.g. the tag push awaiting the user's! git push); CI coverage proven at publish (or explicitly UNVERIFIED for an annotated hotfix exception); deploy state (or "deploy verification needs cluster access I don't have"). Since the publish runs in the background (step 2'srelease-watch.sh), the summary may go out while it is still publishing — say so, and update oncerelease-verify.shconfirms. - What shipped, by issue/PR: one table over the release's CHANGELOG section (every shipping merge since the previous stable tag, as the oracle enforces): PR | issue | what it delivered in 1-2 sentences.
- Behavior changes to flag: anything that changes existing behavior, plus post-deploy caveats.
- Left out: open PRs deliberately not in this release, and why.
- Follow-ups: release-mechanics gaps this cut surfaced; offer to file issues.