Commit 66b302a
fix(release): harden npm-publish pipeline + document release SOP (#744)
* fix(release): harden npm-publish pipeline + document release SOP
Three real failure modes observed during the v5.2.5/5.2.6 releases, each
fixed and documented so the next release does not re-discover them.
## npm-publish.yml
### Race: publish_legacy_aiox_core ran in parallel with publish
The legacy compat wrapper `aiox-core@X.Y.Z` declares
`@aiox-squads/core@X.Y.Z` as a runtime dependency. The smoke test step
runs `npx --yes aiox-core@X.Y.Z --version`, which transitively installs
the scoped package. With both jobs in parallel, `npx` raced against npm
CDN propagation of the scoped package and timed out after 90s — making
the entire job appear failed even though the publish step inside it
had already printed "✅ Published aiox-core@X.Y.Z".
Fix: `publish_legacy_aiox_core` now `needs: [test, build, publish]`,
serializing the legacy publish behind the scoped publish.
### Smoke test legacy npx: timeout too short, no diagnostics
Bumped retry budget 6×15s (90s) → 16×15s (240s). Added a second
visibility check for the scoped dependency before invoking `npx` so we
distinguish "compat not visible" from "scoped dep not visible". Captured
last `npx` stderr in the timeout message instead of failing silently.
### Notify: aggregated bool hid which step failed
Replaced with a structured summary that distinguishes hard fail (scoped
publish) from soft warnings (workspace, legacy, smoke). Only the scoped
publish is treated as release-blocking; the rest are informational and
can be republished out-of-band.
## installer-smoke-matrix.yml
### Windows path mangling in node -e via Git Bash
`${{ github.workspace }}` on a Windows runner expands to
`D:\a\aiox-core\aiox-core`. Interpolated into a `node -e "..."` script
running on Git Bash, the backslashes were interpreted as escape
sequences (`\a` → `a`), producing `D:aaiox-coreaiox-core` and
`Cannot find module ...`.
Fix: pass workspace path via `WORKSPACE_DIR` env var, read inside the
script as `process.env.WORKSPACE_DIR`. Applied to all 3 smoke install
steps.
## docs/guides/release-procedure.md
New SOP capturing the entire release pipeline:
- Pre-flight checks (lint, tests, tokens)
- 4-site version bump coordination (root, compat, installer, lock)
- Two-system branch protection bypass dance (modern ruleset +
legacy branch protection both enforce CODEOWNERS independently;
`gh pr merge --admin` bypasses neither in isolation)
- Atomic bypass→merge→restore as one shell block
- Tag-driven publish trigger
- Post-publish verification (registry sanity + artifact inspection)
- Known CI quirks with mitigations
- Rollback procedure (deprecate, don't unpublish)
- Failure modes seen in production with root causes
The SOP cites real PR numbers and incidents so future operators know
why each step exists.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit feedback on PR #744
Two real bugs caught:
## notify step printed "Successfully published" for skipped scoped publish
The previous condition gated the success block on
`PUBLISH_RESULT = success OR skipped` AND pre-seeded the PUBLISHED array
with `@aiox-squads/core@${VERSION}` unconditionally. Result: a workflow
run where `publish` was skipped (e.g. manual dispatch excluding `core`)
would falsely report the scoped package as published.
Fix: PUBLISHED is now built only from jobs that actually succeeded.
A separate branch handles the all-skipped case explicitly. The
success message only renders when something was actually published.
## SOP bypass dance: snapshot reuse could leave protections relaxed
The documented procedure piped the raw `gh api .../rulesets/...`
output back into `gh api -X PUT`. The raw response includes read-only
fields (`id`, `node_id`, `created_at`, `updated_at`, `_links`, `url`)
that GitHub rejects with HTTP 422 on PUT — meaning the restore could
silently fail and leave production in a relaxed state.
Two fixes layered for safety:
1. Snapshot is now separated from restore payload. Raw snapshot stays
on disk only as a diff baseline. Sanitized restore payload is
derived via `jq '{name, target, enforcement, conditions,
bypass_actors, rules}'` — only writable fields.
2. The bypass→merge→restore sequence is wrapped in `set -e` + a
`trap 'restore_protections' EXIT`. The trap fires on ANY exit
(success, failure, signal) and idempotently re-applies the
sanitized restore payloads. If a restore step itself fails, the
trap emits `::error::` annotations so the failure surfaces in the
GitHub Actions UI.
The previous documentation worked in practice because `gh api --input`
tolerates read-only fields in some paths, but that behavior is not
guaranteed. The new procedure is defensively correct under network
failures, manual interrupts, and edge-case API rejections.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 9b837ed commit 66b302a
3 files changed
Lines changed: 366 additions & 46 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
135 | 135 | | |
136 | 136 | | |
137 | 137 | | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
138 | 144 | | |
139 | 145 | | |
140 | 146 | | |
141 | 147 | | |
142 | 148 | | |
143 | 149 | | |
144 | | - | |
| 150 | + | |
145 | 151 | | |
146 | 152 | | |
147 | 153 | | |
| |||
167 | 173 | | |
168 | 174 | | |
169 | 175 | | |
| 176 | + | |
| 177 | + | |
170 | 178 | | |
171 | 179 | | |
172 | 180 | | |
| |||
177 | 185 | | |
178 | 186 | | |
179 | 187 | | |
180 | | - | |
| 188 | + | |
181 | 189 | | |
182 | 190 | | |
183 | 191 | | |
| |||
209 | 217 | | |
210 | 218 | | |
211 | 219 | | |
| 220 | + | |
| 221 | + | |
212 | 222 | | |
213 | 223 | | |
214 | 224 | | |
| |||
219 | 229 | | |
220 | 230 | | |
221 | 231 | | |
222 | | - | |
| 232 | + | |
223 | 233 | | |
224 | 234 | | |
225 | 235 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
354 | 354 | | |
355 | 355 | | |
356 | 356 | | |
357 | | - | |
358 | | - | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
359 | 364 | | |
360 | 365 | | |
361 | 366 | | |
| |||
439 | 444 | | |
440 | 445 | | |
441 | 446 | | |
| 447 | + | |
442 | 448 | | |
443 | | - | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
444 | 455 | | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
452 | 466 | | |
| 467 | + | |
| 468 | + | |
453 | 469 | | |
454 | | - | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
455 | 473 | | |
456 | | - | |
| 474 | + | |
| 475 | + | |
457 | 476 | | |
458 | 477 | | |
459 | 478 | | |
| |||
519 | 538 | | |
520 | 539 | | |
521 | 540 | | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
522 | 548 | | |
523 | | - | |
524 | | - | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
525 | 566 | | |
526 | 567 | | |
527 | 568 | | |
528 | | - | |
529 | | - | |
530 | | - | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
531 | 573 | | |
532 | | - | |
533 | | - | |
534 | | - | |
535 | | - | |
536 | | - | |
| 574 | + | |
| 575 | + | |
537 | 576 | | |
538 | | - | |
539 | | - | |
540 | | - | |
541 | | - | |
542 | | - | |
543 | | - | |
544 | | - | |
| 577 | + | |
| 578 | + | |
545 | 579 | | |
546 | 580 | | |
547 | | - | |
548 | | - | |
549 | | - | |
550 | | - | |
551 | | - | |
552 | | - | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
553 | 590 | | |
554 | 591 | | |
555 | | - | |
556 | | - | |
557 | | - | |
558 | | - | |
559 | | - | |
560 | | - | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
561 | 599 | | |
562 | | - | |
563 | | - | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
564 | 609 | | |
565 | 610 | | |
566 | 611 | | |
| |||
0 commit comments