| 1 | # Codewhale Release Runbook |
| 2 | |
| 3 | This runbook is the source of truth for shipping Rust crates, GitHub release assets, |
| 4 | and the `codewhale` npm wrapper. |
| 5 | |
| 6 | Current packaging note: |
| 7 | - `codewhale-tui` is the live runtime crate linked into the installed |
| 8 | `codewhale`/`codew` commands; it is not a third installed command. |
| 9 | - `codewhale-app-server` is a supporting library crate. The shipped entrypoint |
| 10 | is `codewhale app-server`; do not add or publish a standalone app-server binary. |
| 11 | |
| 12 | ## Canonical Publish Targets |
| 13 | |
| 14 | - End-user crates: |
| 15 | - `codewhale-tui` |
| 16 | - `codewhale-cli` |
| 17 | - Supporting crates published from this workspace: |
| 18 | - `codewhale-build-support` |
| 19 | - `codewhale-mcp` |
| 20 | - `codewhale-paths` |
| 21 | - `codewhale-protocol` |
| 22 | - `codewhale-release` |
| 23 | - `codewhale-secrets` |
| 24 | - `codewhale-state` |
| 25 | - `codewhale-telemetry` |
| 26 | - `codewhale-workflow` |
| 27 | - `codewhale-workflow-js` |
| 28 | - `codewhale-execpolicy` |
| 29 | - `codewhale-hooks` |
| 30 | - `codewhale-tools` |
| 31 | - `codewhale-config` |
| 32 | - `codewhale-cloud-facts` |
| 33 | - `codewhale-lane` |
| 34 | - `codewhale-agent` |
| 35 | - `codewhale-core` |
| 36 | - `codewhale-command-contract` |
| 37 | - `codewhale-app-server` |
| 38 | |
| 39 | ## Version Coordination |
| 40 | |
| 41 | - Rust crates inherit the shared workspace version from [Cargo.toml](../Cargo.toml). |
| 42 | - Internal path dependency versions should match the shared workspace version; stale older pins are release blockers once the workspace version moves. |
| 43 | - The npm wrapper version lives in [npm/codewhale/package.json](../npm/codewhale/package.json). |
| 44 | - `codewhaleBinaryVersion` controls which GitHub release binaries the npm wrapper downloads. |
| 45 | - Packaging-only npm releases are allowed: |
| 46 | - bump the npm package version |
| 47 | - leave `codewhaleBinaryVersion` pinned to the previously released Rust binaries |
| 48 | - rerun `npm pack` smoke checks before `npm publish` |
| 49 | |
| 50 | ## Release Source Timing |
| 51 | |
| 52 | Freeze the source before creating a public `vX.Y.Z` tag. The version bump is |
| 53 | not the release; it is the last source-prep commit before the tag. Do not keep |
| 54 | merging same-version feature/fix PRs after `vX.Y.Z` exists and assume the |
| 55 | release workflow will pick them up. It will not: the tag is the release anchor. |
| 56 | |
| 57 | Before tagging, verify the live queue and existing anchors: |
| 58 | |
| 59 | ```bash |
| 60 | gh issue list --repo Hmbown/CodeWhale --milestone "vX.Y.Z" --state open |
| 61 | gh pr list --repo Hmbown/CodeWhale --state open --limit 100 |
| 62 | git ls-remote origin refs/heads/main refs/tags/vX.Y.Z |
| 63 | gh release view vX.Y.Z --repo Hmbown/CodeWhale |
| 64 | ./scripts/release/check-published.sh X.Y.Z |
| 65 | ``` |
| 66 | |
| 67 | If a same-version tag already exists but there is no GitHub Release and nothing |
| 68 | is published, stop and choose deliberately: |
| 69 | |
| 70 | - publish exactly the tagged SHA, leaving later commits for the next patch; |
| 71 | - bump the later work to the next patch version and tag that later SHA; or |
| 72 | - with explicit maintainer approval only, delete/recreate the unpublished tag |
| 73 | after confirming no package, GitHub Release, mirror, or installer consumer has |
| 74 | treated it as public. |
| 75 | |
| 76 | Do not delete, move, or recreate a release tag implicitly as part of ordinary |
| 77 | PR merge or milestone cleanup work. |
| 78 | |
| 79 | ## Preflight |
| 80 | |
| 81 | Run these from the repository root before cutting a tag: |
| 82 | |
| 83 | ```bash |
| 84 | ./scripts/release/check-versions.sh # workspace/npm/SDK/VS Code/generated-fact/lock drift |
| 85 | cargo fmt --all -- --check |
| 86 | cargo check --workspace --all-targets --locked |
| 87 | cargo clippy --workspace --all-targets --all-features --locked -- -D warnings |
| 88 | cargo test --workspace --all-features --locked |
| 89 | ./scripts/release/publish-crates.sh dry-run |
| 90 | ``` |
| 91 | |
| 92 | `check-versions.sh` also runs in CI on every push/PR (the `versions` job in |
| 93 | `.github/workflows/ci.yml`), so drift between `Cargo.toml`, the per-crate |
| 94 | manifests, the npm wrapper and Runtime SDK, the VS Code extension and lock, |
| 95 | generated web facts, and `Cargo.lock` is caught before release time rather than |
| 96 | at it. |
| 97 | |
| 98 | The source-controlled CNB pipeline mirrors the heavy Linux version/fmt/check/ |
| 99 | clippy/test/npm-smoke gates for `fix/*`, `rebrand/*`, `work/v*`, and `main`. |
| 100 | GitHub Actions keeps the cheap drift/fmt statuses plus macOS and Windows |
| 101 | coverage, while CNB carries the Linux work. |
| 102 | |
| 103 | `publish-crates.sh` requires Cargo 1.90 or newer for multi-package verification; |
| 104 | this release-tool requirement is separate from the runtime's Rust 1.88 MSRV. |
| 105 | Use an up-to-date stable toolchain (`rustup update stable`) for release work. |
| 106 | |
| 107 | Both modes validate publication order against the locked workspace graph, then |
| 108 | run one `cargo publish --dry-run --locked --registry crates-io` covering all |
| 109 | release crates listed in `scripts/release/crates.sh`. Cargo resolves unpublished |
| 110 | workspace dependencies through a |
| 111 | temporary local registry, builds every unpacked tarball, and checks publication |
| 112 | metadata before any upload. Dry-run mode permits source edits and stops there. |
| 113 | Publish mode requires the approved release checkout and assets, then skips |
| 114 | versions already on crates.io and uploads the remaining crates in dependency |
| 115 | order. Resuming still verifies the complete source release; it never weakens |
| 116 | the artifact gate merely because an earlier crate was already uploaded. |
| 117 | Registry-side acceptance and credentials are still checked during real upload; |
| 118 | a successful preflight cannot guarantee that every later upload will succeed. |
| 119 | |
| 120 | For npm wrapper verification, build the single runtime and run the |
| 121 | cross-platform smoke harness. This packs the npm wrapper, installs it into a |
| 122 | clean temporary project, serves local release assets over HTTP, and checks both |
| 123 | published commands against that runtime: `codewhale doctor --help` and |
| 124 | `codew --version`. |
| 125 | |
| 126 | ```bash |
| 127 | cargo build --release --locked -p codewhale-cli -p codewhale-tui |
| 128 | node scripts/release/npm-wrapper-smoke.js |
| 129 | ``` |
| 130 | |
| 131 | Set `DEEPSEEK_TUI_KEEP_SMOKE_DIR=1` to keep the temporary pack/install |
| 132 | directory for inspection. |
| 133 | |
| 134 | ## Exact-head GitHub proof before publication |
| 135 | |
| 136 | Two manual workflows provide exact-head evidence without crossing the public |
| 137 | release boundary. Run them only after the intended source is on a named ref |
| 138 | (normally the frozen `main`), and pass the full commit SHA as an independent |
| 139 | guard against that ref moving between inspection and dispatch: |
| 140 | |
| 141 | ```bash |
| 142 | git fetch origin main |
| 143 | candidate_sha="$(git rev-parse origin/main)" |
| 144 | |
| 145 | gh workflow run ci.yml --ref main \ |
| 146 | -f expected_sha="${candidate_sha}" |
| 147 | gh workflow run release-candidate.yml --ref main \ |
| 148 | -f expected_sha="${candidate_sha}" |
| 149 | ``` |
| 150 | |
| 151 | The manual `ci.yml` path verifies that the dispatch resolved to |
| 152 | `expected_sha`, disables light-change shortcuts, and forces the heavy Rust, |
| 153 | workflow, mobile, Actions, Linux, macOS, Windows, npm-wrapper, and documentation |
| 154 | gates. A mismatch fails before those gates start; it never silently tests a |
| 155 | different head. |
| 156 | |
| 157 | `release-candidate.yml` also fails unless the selected ref resolves to the |
| 158 | exact requested SHA. It invokes the same reusable artifact workflow as the |
| 159 | public release, building all seven targets (including Android arm64 and native |
| 160 | Windows arm64), staging `codewhale` and `codew` (single binary), building the |
| 161 | NSIS installer and nine platform archives, and validating the authoritative |
| 162 | 34-file inventory from `npm/codewhale/scripts/artifacts.js` (27 current |
| 163 | artifacts and manifests plus seven compatibility-only `codewhale-tui-*` |
| 164 | filenames containing the same compiled `codewhale` bytes for v0.9.4 update |
| 165 | clients). It then installs |
| 166 | the packed npm wrapper against those assembled local assets and exercises its |
| 167 | delegated entrypoints. The resulting `codewhale-release-assets` bundle is a |
| 168 | short-lived GitHub Actions artifact only. |
| 169 | |
| 170 | This candidate workflow does not create a tag or GitHub Release, publish a |
| 171 | crate or npm package, push a container, update Homebrew, deploy anything, or |
| 172 | write repository contents. Its green result is evidence, not publication |
| 173 | authorization. The stop line remains explicit Hunter approval: do not create |
| 174 | the `vX.Y.Z` tag, dispatch `release.yml`, or run any registry publication step |
| 175 | until that approval is given. |
| 176 | |
| 177 | The Android target is cross-built and included in the checksum/bundle gates, |
| 178 | but GitHub's Linux runner cannot execute the Android binary as a real Termux |
| 179 | user. Keep the real-device limitation in the release packet unless separate |
| 180 | device evidence exists. |
| 181 | |
| 182 | To exercise `npm run release:check` locally as well, regenerate the local asset |
| 183 | directory with a full asset matrix fixture before starting the server: |
| 184 | |
| 185 | ```bash |
| 186 | DEEPSEEK_TUI_PREPARE_ALL_ASSETS=1 node scripts/release/prepare-local-release-assets.js |
| 187 | cd npm/codewhale |
| 188 | DEEPSEEK_TUI_VERSION=X.Y.Z DEEPSEEK_TUI_RELEASE_BASE_URL=http://127.0.0.1:8123/ npm run release:check |
| 189 | ``` |
| 190 | |
| 191 | Set `DEEPSEEK_TUI_VERSION` to the npm package version you are verifying for that local run. |
| 192 | |
| 193 | The CNB workflow runs the Linux tarball install + delegated-entrypoint smoke |
| 194 | test; GitHub Actions keeps macOS and Windows smoke coverage. |
| 195 | |
| 196 | After publishing, prove the release is visible in both registries: |
| 197 | |
| 198 | ```bash |
| 199 | ./scripts/release/check-published.sh X.Y.Z |
| 200 | ``` |
| 201 | |
| 202 | Do not mark a Rust release complete until that command sees `codewhale@X.Y.Z` |
| 203 | on npm and every `codewhale-*` crate at `X.Y.Z` on crates.io. For a rare |
| 204 | npm packaging-only release, run with `--allow-npm-binary-mismatch` and keep the |
| 205 | release notes explicit that no new Rust binary version shipped. |
| 206 | |
| 207 | ## Post-Merge Branch Hygiene |
| 208 | |
| 209 | After a release or scratch integration branch lands, run the branch hygiene |
| 210 | helper before pruning anything: |
| 211 | |
| 212 | ```bash |
| 213 | ./scripts/release/branch-hygiene.sh --release-branch codex/vX.Y.Z |
| 214 | ``` |
| 215 | |
| 216 | The default mode is a dry run. It reports the current checkout branch, main ref, |
| 217 | local and remote release tips, safe local or remote branch deletes, branches |
| 218 | kept for contributor work, and branches that still need a human decision. Review |
| 219 | that report before running `--prune --yes`, and add `--prune-remote` only when |
| 220 | you have confirmed the remote branches are safe to delete. |
| 221 | |
| 222 | Use `--remote upstream` when you are working from a fork and the canonical |
| 223 | release refs live on the upstream remote instead of `origin`. |
| 224 | |
| 225 | Verify the helper itself after changing it: |
| 226 | |
| 227 | ```bash |
| 228 | bash scripts/release/branch-hygiene.test.sh |
| 229 | bash scripts/release/ensure-release-on-main.test.sh |
| 230 | ``` |
| 231 | |
| 232 | Those scripts are pinned to LF line endings so the same command works from a |
| 233 | Windows checkout under Bash. |
| 234 | |
| 235 | ## Rust Crates Release |
| 236 | |
| 237 | Crate publishing to crates.io is **manual** — there is no automated |
| 238 | `crates-publish` GitHub workflow. Operators run the helpers in |
| 239 | `scripts/release/` from a developer workstation that has `cargo login` |
| 240 | configured. |
| 241 | |
| 242 | Release commits must land on `main` before any `vX.Y.Z` tag is pushed. Do not |
| 243 | tag a release-only branch. Open the release PR against `main`, let required |
| 244 | review and CI finish, merge it, then explicitly tag the final source commit |
| 245 | that is reachable from `main`. This is what lets GitHub process `Closes #N` |
| 246 | lines automatically and show the release PR as merged. The tag release workflow runs |
| 247 | `scripts/release/ensure-release-on-main.sh` for tag pushes and manual dispatches, |
| 248 | and fails branch-only release sources before assets are published. |
| 249 | |
| 250 | 1. Write the CHANGELOG entry, then run |
| 251 | `./scripts/release/prepare-release.sh X.Y.Z` — it bumps every |
| 252 | version-bearing file (workspace + crate pins + npm wrapper + Runtime SDK + |
| 253 | VS Code extension/lock + remote-smoke default + public source-candidate |
| 254 | facts + README install tags), refreshes the Cargo/npm locks and generated |
| 255 | files, and runs |
| 256 | the version and OHOS gates. It is safe to rerun after the workspace already |
| 257 | equals `X.Y.Z`: the second run skips replacements, refreshes the packaged |
| 258 | changelog and web facts, and reruns both gates. |
| 259 | 2. Run `./scripts/release/publish-crates.sh dry-run` locally; it must be clean. |
| 260 | 3. Merge the release PR into `main` before tagging. After the same-version |
| 261 | queue is frozen and `main` is at the intended source SHA, create `vX.Y.Z` |
| 262 | from `main` with the manual **Create release tag** workflow or with a signed |
| 263 | local tag push from a developer machine. |
| 264 | - If `RELEASE_TAG_PAT` is configured, the tag push starts `release.yml`. |
| 265 | - If no Release run appears and the tag already exists, first confirm that |
| 266 | no tag-triggered run is queued or active, then dispatch the exact tag: |
| 267 | `gh workflow run release.yml --ref vX.Y.Z -f version=X.Y.Z`. |
| 268 | - Never dispatch from `main`, and do not start a duplicate while the |
| 269 | tag-triggered run is merely delayed. The workflow serializes runs for the |
| 270 | same tag. It also refuses to start release work when that tag already owns |
| 271 | any GitHub Release asset, rechecks immediately before upload, and disables |
| 272 | the release action's overwrite behavior. A normal rerun must never replace |
| 273 | public bytes. |
| 274 | 4. Wait for the GitHub Release workflow and all public assets to finish, then |
| 275 | fetch the release tag and run the public asset gate. Do not publish any |
| 276 | Cargo or npm package until it passes: |
| 277 | |
| 278 | ```bash |
| 279 | git fetch --force origin +refs/tags/vX.Y.Z:refs/tags/vX.Y.Z |
| 280 | ./scripts/release/verify-release-assets.sh X.Y.Z |
| 281 | ``` |
| 282 | |
| 283 | 5. Create a clean detached checkout of the immutable release tag, then publish |
| 284 | the Rust crates from that checkout only: |
| 285 | |
| 286 | ```bash |
| 287 | git worktree add --detach ../codewhale-release-vX.Y.Z vX.Y.Z |
| 288 | cd ../codewhale-release-vX.Y.Z |
| 289 | ./scripts/release/require-release-tag-checkout.sh X.Y.Z |
| 290 | ./scripts/release/publish-crates.sh publish |
| 291 | ``` |
| 292 | |
| 293 | Both Cargo and npm publication fail closed unless `HEAD`, the clean local |
| 294 | checkout, and the remote `vX.Y.Z` tag still agree. The authoritative crate |
| 295 | dependency order lives in `scripts/release/crates.sh`; do not maintain a |
| 296 | second handwritten order in this runbook. The helper waits for each new |
| 297 | version to appear on crates.io before moving to dependents and safely skips |
| 298 | versions that are already public on a rerun. |
| 299 | |
| 300 | The publish helper is idempotent for reruns: already-published crate versions are skipped. |
| 301 | |
| 302 | ## GitHub Release Assets |
| 303 | |
| 304 | `.github/workflows/release.yml` builds and stages these artifacts: |
| 305 | |
| 306 | - one `codewhale-*` runtime binary for Linux x64/arm64, Android arm64, macOS |
| 307 | x64/arm64, and Windows x64/arm64 |
| 308 | - byte-identical `codew-*` command assets copied from that runtime |
| 309 | - byte-identical `codewhale-tui-*` compatibility filenames so installed v0.9.4 |
| 310 | clients can discover and complete the one-runtime upgrade; current installers |
| 311 | never expose those filenames as a third command |
| 312 | - `codewhale.bat` for the Windows npm/GitHub x64 launcher, and the same |
| 313 | filename inside Windows zip archives and the NSIS install (those copies |
| 314 | launch `codewhale.exe` and prefer Windows Terminal) |
| 315 | - platform `.tar.gz` / `.zip` archives and `CodeWhaleSetup.exe` |
| 316 | |
| 317 | The release job also uploads `codewhale-artifacts-sha256.txt` and |
| 318 | `codewhale-bundles-sha256.txt`. The npm installer and release verification |
| 319 | script depend on those manifests. The authoritative release asset list lives in |
| 320 | `npm/codewhale/scripts/artifacts.js`. |
| 321 | |
| 322 | Before any Cargo or npm publish, prove that the public GitHub Release assets |
| 323 | belong to the tag commit you are publishing: |
| 324 | |
| 325 | ```bash |
| 326 | ./scripts/release/verify-release-assets.sh X.Y.Z |
| 327 | ``` |
| 328 | |
| 329 | That gate compares the local and remote `vX.Y.Z` tag SHAs, confirms a |
| 330 | successful `Release` workflow run used that SHA, then runs the npm wrapper's |
| 331 | release check against the public GitHub asset URLs. The npm check fails if the |
| 332 | release is missing a required binary, archive, installer, or manifest; either |
| 333 | manifest omits a required row; or the assets predate the matching release |
| 334 | workflow run. If the command fails, rerun or repair `release.yml`; do not |
| 335 | publish Cargo or npm against stale assets. |
| 336 | |
| 337 | ## AUR / Omarchy Package |
| 338 | |
| 339 | `codewhale-bin` is a downstream package of the same Linux release, not a new |
| 340 | Codewhale semantic version. After the public asset gate above passes, render |
| 341 | its AUR metadata from the verified release directory: |
| 342 | |
| 343 | ```bash |
| 344 | ./packaging/aur/render.sh /path/to/release-assets /tmp/codewhale-bin |
| 345 | ``` |
| 346 | |
| 347 | The renderer reads the workspace version and extracts the x64/arm64 archive |
| 348 | hashes only after both release checksum manifests agree with the actual files. |
| 349 | It emits no `SKIP` checksums or source-controlled per-release values. Follow |
| 350 | [`packaging/aur/README.md`](../packaging/aur/README.md) for the clean Arch build, |
| 351 | `.SRCINFO` comparison, and package-content checks. |
| 352 | |
| 353 | The GitHub release workflows only verify that the AUR metadata can be rendered |
| 354 | from their candidate assets. They do not publish to AUR. AUR publication is a |
| 355 | separate, explicitly authorized maintainer action after the matching tag and |
| 356 | assets are public. |
| 357 | |
| 358 | ## npm Wrapper Release |
| 359 | |
| 360 | `release.yml` publishes `codewhale` through npm Trusted Publishing after the |
| 361 | exact-SHA GitHub Release job succeeds. The job has only `contents: read` and |
| 362 | `id-token: write`; it does not use `NPM_TOKEN`, `NODE_AUTH_TOKEN`, or a |
| 363 | long-lived bypass-2FA credential. |
| 364 | |
| 365 | Before the first automated publish, configure the `codewhale` package's npm |
| 366 | Trusted Publisher with these exact values: |
| 367 | |
| 368 | - organization or user: `Hmbown` |
| 369 | - repository: `CodeWhale` |
| 370 | - workflow filename: `release.yml` |
| 371 | - environment: leave blank (the workflow does not claim a GitHub environment) |
| 372 | |
| 373 | That npm-side binding is an external release gate. If it is missing or differs |
| 374 | in case, repository, workflow filename, or environment, the publish job must |
| 375 | fail; do not add a token fallback to make it pass. |
| 376 | |
| 377 | ### Steps |
| 378 | |
| 379 | 1. Set the npm package version in [npm/codewhale/package.json](../npm/codewhale/package.json) to match the workspace `Cargo.toml`. CI's version-drift guard will catch mismatches before tag. |
| 380 | 2. Set `codewhaleBinaryVersion` to the GitHub release tag that should supply binaries. |
| 381 | 3. Push the version bump to `main`. After the release source is frozen, create |
| 382 | the matching `vX.Y.Z` tag from `main`; `release.yml` then builds the binary |
| 383 | matrix and publishes the GitHub Release after its artifact and container gates. |
| 384 | The tag also syncs to `cnb.cool/codewhale.net/codewhale`, whose pipeline |
| 385 | independently publishes a Linux x64 release and marks it latest. Include |
| 386 | that destination in publication approval; it does not wait for GitHub Release. |
| 387 | 4. **Wait for the GitHub Release to finalize** with the full binary and archive |
| 388 | matrix, Windows installer, and both checksum manifests. The dependent `npm` |
| 389 | job checks the remote tag again, runs the public asset freshness gate and |
| 390 | package tests, then publishes with OIDC. The package's `prepublishOnly` hook |
| 391 | repeats the clean exact-tag and public-asset checks immediately before the |
| 392 | registry write. |
| 393 | 5. Confirm the `npm` job succeeded, then prove the published package and binary |
| 394 | version are visible: |
| 395 | |
| 396 | ```bash |
| 397 | npm view codewhale@X.Y.Z version codewhaleBinaryVersion --json |
| 398 | ./scripts/release/check-published.sh X.Y.Z |
| 399 | ``` |
| 400 | |
| 401 | For a rare packaging-only npm release where the npm package version intentionally |
| 402 | points at older Rust binaries, add `--allow-npm-binary-mismatch` and keep the |
| 403 | release notes explicit that no new binary version shipped. That exception is a |
| 404 | separate manual release path: the normal trusted-publishing job deliberately |
| 405 | does not set `CODEWHALE_ALLOW_NPM_BINARY_MISMATCH`. |
| 406 | |
| 407 | Do not publish `npm/deepseek-tui`; it is deprecated compatibility metadata only. |
| 408 | |
| 409 | ### Manual recovery |
| 410 | |
| 411 | If GitHub OIDC is unavailable after the GitHub Release and public-asset gate are |
| 412 | green, use a clean detached checkout of the immutable tag. Authenticate |
| 413 | interactively with npm's normal WebAuthn/2FA flow; never create a long-lived |
| 414 | bypass-2FA token: |
| 415 | |
| 416 | ```bash |
| 417 | ./scripts/release/require-release-tag-checkout.sh X.Y.Z |
| 418 | ./scripts/release/verify-release-assets.sh X.Y.Z |
| 419 | npm login |
| 420 | npm whoami |
| 421 | cd npm/codewhale |
| 422 | npm publish --access public |
| 423 | ``` |
| 424 | |
| 425 | The same `prepublishOnly` gates rerun on every attempt. An OIDC or login failure |
| 426 | is not permission to edit the tagged package, move the tag, or skip asset |
| 427 | verification. |
| 428 | |
| 429 | ## CNB Cool mirror |
| 430 | |
| 431 | Every push to `main`, `fix/*`, `rebrand/*`, `work/v*`, and every `v*` tag is mirrored to |
| 432 | `cnb.cool/codewhale.net/codewhale` via the `Sync to CNB` workflow |
| 433 | so users behind GitHub-blocking networks can fetch the source and so CNB can |
| 434 | run the heavy Linux CI lane. After a release tag, **verify the mirror caught |
| 435 | it** before declaring the release shipped: |
| 436 | |
| 437 | ```bash |
| 438 | git ls-remote https://cnb.cool/codewhale.net/codewhale.git refs/tags/vX.Y.Z |
| 439 | ``` |
| 440 | |
| 441 | If the workflow failed for the release tag, use the exact-tag rerun or |
| 442 | `workflow_dispatch --ref vX.Y.Z` recovery documented in |
| 443 | [docs/CNB_MIRROR.md](CNB_MIRROR.md#manual-fallback). |
| 444 | |
| 445 | ## Recovery and Rollback |
| 446 | |
| 447 | ### Re-tagging an UNPUBLISHED release (pulled before npm/crates) |
| 448 | |
| 449 | If `vX.Y.Z` was tagged and a GitHub Release was created, but **no package was |
| 450 | published** (npm still on the prior version, `check-published.sh` shows the |
| 451 | crates unpublished), the tag is still recoverable — a bug found after tagging |
| 452 | can be fixed and the same version recut. Confirm first that nothing consumed it: |
| 453 | `npm view codewhale version` and crates.io both show the PRIOR version, no |
| 454 | Homebrew/Winget/mirror points at it, and the GitHub Release download counts are |
| 455 | only the release pipeline's own verification passes. Then, with explicit |
| 456 | maintainer approval: |
| 457 | |
| 458 | ```bash |
| 459 | # 1. land the fix on main (normal PR + required CI) |
| 460 | # 2. delete the premature Release + tag |
| 461 | gh release delete vX.Y.Z --repo Hmbown/CodeWhale --yes --cleanup-tag |
| 462 | git push origin :refs/tags/vX.Y.Z # belt-and-suspenders |
| 463 | git tag -d vX.Y.Z # local |
| 464 | # 3. recut at the fixed HEAD (workspace version unchanged) |
| 465 | gh workflow run auto-tag.yml --repo Hmbown/CodeWhale --ref main |
| 466 | # 4. release.yml rebuilds assets; rebuild + reinstall locally from the new tag |
| 467 | ``` |
| 468 | |
| 469 | This is the sanctioned path from "do not delete/move/recreate a release tag |
| 470 | implicitly": it is explicit, approved, and only for a tag no registry consumer |
| 471 | has treated as public. Never do it once a crate or the npm wrapper is published |
| 472 | for that version — bump to the next patch instead. |
| 473 | |
| 474 | ### External publish gates (not code defects) |
| 475 | |
| 476 | - **crates.io:** publishing needs a valid `cargo login` token on the operator |
| 477 | machine (`curl -H "Authorization: <token>" https://crates.io/api/v1/me` |
| 478 | returning 200). A 403 means the token is missing/expired — `cargo login`, |
| 479 | then `./scripts/release/publish-crates.sh publish`. |
| 480 | - **npm:** the OIDC job publishes only if the npmjs.com Trusted Publisher for |
| 481 | `Hmbown` / `CodeWhale` / workflow `release.yml` / blank environment is |
| 482 | configured. Missing config → the `npm` job fails `E404 No match found`. Fix |
| 483 | the binding (or use the manual WebAuthn recovery above), then re-run the |
| 484 | failed `npm` job: `gh run rerun <release-run-id> --failed`. |
| 485 | |
| 486 | - User-facing rollback: |
| 487 | - npm: `npm install -g codewhale@X.Y.Z` |
| 488 | - Cargo: `cargo install codewhale-cli --version X.Y.Z --locked --force`; |
| 489 | add an optional `codew` alias as documented in |
| 490 | [docs/INSTALL.md](INSTALL.md#7-build-from-source) |
| 491 | - manual assets: download binaries or the platform archive plus the matching |
| 492 | `codewhale-artifacts-sha256.txt` or `codewhale-bundles-sha256.txt` |
| 493 | manifest from `https://github.com/Hmbown/CodeWhale/releases/tag/vX.Y.Z` |
| 494 | - workspace files: use `/restore list [N]` and `/restore <N>` for side-git |
| 495 | snapshots; this does not change the installed binary version or rewrite |
| 496 | conversation history |
| 497 | - keep [docs/INSTALL.md](INSTALL.md#roll-back-to-a-previous-release) in sync |
| 498 | with these commands |
| 499 | - Crates publish partially: |
| 500 | - rerun `./scripts/release/publish-crates.sh publish` |
| 501 | - already-published crate versions will be skipped |
| 502 | - GitHub assets missing or checksum manifest incomplete: |
| 503 | - fix `.github/workflows/release.yml`, but do not rerun it over an existing |
| 504 | asset set and do not delete assets merely to make the guard pass |
| 505 | - if any asset may have been public or consumed, cut a new patch version |
| 506 | - only after explicit maintainer approval and proof that no downstream |
| 507 | publication or consumer treated the failed asset set as public may a |
| 508 | deliberately scoped recovery remove the failed release before an exact-tag |
| 509 | rerun; record that exception in the release packet |
| 510 | - npm packaging-only problem: |
| 511 | - bump only the npm package version |
| 512 | - keep `codewhaleBinaryVersion` on the last known-good Rust release |
| 513 | - repack and republish the wrapper |
| 514 | - A bad npm publish cannot be overwritten: |
| 515 | - publish a new npm version with corrected metadata or install logic |
| 516 | - CNB mirror failed for the release tag: |
| 517 | - check the run via `gh run list --workflow=sync-cnb.yml` |
| 518 | - rerun the failed tag run, or dispatch |
| 519 | `gh workflow run sync-cnb.yml --ref vX.Y.Z`; never omit the tag ref |
| 520 | - follow the proof steps in |
| 521 | [docs/CNB_MIRROR.md](CNB_MIRROR.md#manual-fallback) |
| 522 | - Workflow runner failure or hung release job: |
| 523 | - every release-lane job carries an explicit `timeout-minutes` to contain |
| 524 | unattended runs, but timeouts are containment rather than immediate recovery |
| 525 | - if a workflow job sits `in_progress` with 404 logs (or produces no useful |
| 526 | log output for 20 minutes), cancel the run and rerun failed jobs / dispatch |
| 527 | an exact-ref rerun rather than waiting out the full job timeout |
| 528 | - check the last-useful-log timestamp before cancelling to distinguish an |
| 529 | infrastructure failure (runner dropped / HTTP 404 on log stream) from a |
| 530 | legitimate long build step (e.g. Windows artifact compilation) |
| 531 |