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