feat: add reusable release.yml workflow (SK-417)#9
Conversation
Port the estate's hardened release flow (build-once -> npm OIDC Trusted Publishing -> GitHub Packages mirror -> tag + GitHub release; ASW-328/326/323) into a workflow_call reusable workflow, replacing the per-repo release.yml copies that would otherwise drift (SK-384). Three least-privileged jobs: an unprivileged build job that packs the single tarball both legs ship; an npm leg publishing via OIDC (no NPM_TOKEN) with a version-vs-tag gate, tag + release, and failure notification; and a separate GitHub Packages mirror job carrying packages:write and a build-provenance attestation over the same tarball. Drops the two per-repo couplings of the reference: the local load-repo-config action becomes with: inputs, and the infrastructure/scripts/publish-*.sh scripts are inlined to centralise the logic. The build job reuses the Layer-1 setup-project + build actions; the publish legs are hand-rolled (they need setup-node registry-url/scope) and pin pnpm/setup-node to the same SHAs. No secrets: block — OIDC + the automatic GITHUB_TOKEN cover everything; only permissions cross the caller boundary. Document the npm-validates-the-caller OIDC nuance, the branch-protected npm-release environment prerequisite, and the caller-owns-trigger / no-workflow_dispatch rule in CLAUDE.md.
|
Claude finished @RobEasthope's task in 6m 15s —— View job Code Review:
|
|
Warning Review limit reached
More reviews will be available in 48 minutes and 53 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a reusable release workflow that builds one npm tarball, gates publishing on tag/version state, publishes to npm with OIDC, optionally mirrors to GitHub Packages with attestation, creates a GitHub Release, and updates failure tracking. ChangesReusable release workflow
Sequence Diagram(s)sequenceDiagram
participant "Caller workflow" as CallerWorkflow
participant "reusable release workflow" as ReusableWorkflow
participant "build job" as BuildJob
participant "GitHub Actions artefact" as ArtifactStore
participant "release job" as ReleaseJob
participant "npm registry" as NpmRegistry
participant "GitHub Releases" as GitHubReleases
participant "GitHub Issues" as GitHubIssues
CallerWorkflow->>ReusableWorkflow: workflow_call
ReusableWorkflow->>BuildJob: run build job
BuildJob->>ArtifactStore: upload npm tarball
ReusableWorkflow->>ReleaseJob: run release job
ReleaseJob->>ArtifactStore: download npm tarball
ReleaseJob->>NpmRegistry: npm view version tag
alt version tag is missing
ReleaseJob->>NpmRegistry: npm publish tarball
ReleaseJob->>GitHubReleases: create release for the version tag
end
alt release job fails
ReleaseJob->>GitHubIssues: open or append failure-tracking issue
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/reusable-release.yml:
- Around line 421-450: The current failure notifier in the release workflow only
lives inside the main release job, so mirror-job failures can end the run
without opening or updating the tracking issue. Add a separate final notifier
job that depends on both publish jobs and uses the same issue/comment logic as
the existing “Notify on release failure” step, with a workflow-level failure
condition so it runs when either publish leg fails. Keep the existing GH issue
search/create/comment behavior, but move or duplicate it into that final job so
failures from the mirror path are always captured.
- Around line 243-246: The release flow in reusable-release.yml is using the
tag-based should_publish gate too early, which can let GitHub Packages publish
before the mirror leg finishes and then skip the mirror on reruns. Update the
workflow around the should_publish output, the tag creation/release step, and
the publish jobs so tagging happens only after the optional mirror completes, or
derive each registry’s publish decision from its own state instead of tag
existence. Focus on the reusable-release job outputs, the tag/release creation
block, and the mirror/publish job ordering.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d0420031-14cf-4961-8091-8bbc6fa5fbe2
📒 Files selected for processing (2)
.github/workflows/reusable-release.ymlCLAUDE.md
| outputs: | ||
| # Reused by publish-github-packages so it gates on the same condition | ||
| # without re-detecting (true = version bumped, not yet tagged = publish). | ||
| should_publish: ${{ steps.gate.outputs.publish }} |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Defer tag creation until after the mirror leg completes.
Line 246 makes the GitHub Packages job depend on the tag-based should_publish gate, but Line 412 creates that tag before the mirror runs. If the first run reaches the GitHub release step and the mirror later fails, a rerun flips should_publish to false and skips the mirror entirely, leaving npm and GitHub Packages out of sync. Move the tag/release step into a final job after the optional mirror, or gate each registry from its own publish state instead of tag existence.
Also applies to: 379-417, 459-467
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/reusable-release.yml around lines 243 - 246, The release
flow in reusable-release.yml is using the tag-based should_publish gate too
early, which can let GitHub Packages publish before the mirror leg finishes and
then skip the mirror on reruns. Update the workflow around the should_publish
output, the tag creation/release step, and the publish jobs so tagging happens
only after the optional mirror completes, or derive each registry’s publish
decision from its own state instead of tag existence. Focus on the
reusable-release job outputs, the tag/release creation block, and the
mirror/publish job ordering.
…7 review) Address the AI code-review findings on PR #9: - Assert exactly one tarball in both "Resolve tarball path" steps (mirroring the pack step) so a corrupt/multi-file artifact fails loudly rather than as a confusing multi-line $TARBALL "no such file" mid-publish (claude-review). - Gate the release job's artifact download + resolve on should_publish so a no-op feature merge skips the wasted download (claude-review). - Move the failure notifier into a dedicated terminal job that needs all three jobs (build + both publish legs) and fires on if: failure(). Previously the notify step lived only in the release job, so a GitHub Packages mirror failure (which lands after the npm leg has gone green) ended the run silently with no tracking issue (CodeRabbit). Now build, npm and mirror failures are all captured; the release job drops issues: write accordingly. Deferred (SK-457): CodeRabbit's tag-before-mirror rerun desync — a pre-existing property of the estate's reference release.yml; the fix should land on both the reference and this reusable copy, out of scope for the port.
Review triage (claude-review + CodeRabbit)Addressed in
All checks green before these changes; actionlint/yamllint/markdownlint/prettier clean locally on the update. |
Incremental review pass (on
|
Triaged on the PR: the actionable findings (notifier, tarball guard, gated download) are fixed in f7b8949; the remaining tag-before-mirror item is a pre-existing property of the estate reference, deferred to SK-457. Dismissing the stale review to merge.
## What Reconstructs `shared-workflows`' **entire release history** and graduates the current code to a stable **`v1.0.0`** (A-585). The repo publishes no npm package, so a "release" here is a **git tag (`vX.Y.Z`) + GitHub release** over the reusable workflows and composite actions. ## Versioning methodology The merged PR **titles are not reliable** — Conventional-Commit discipline was loose when the repo was stood up (the GO/NO GO gate shipped as `ci:`, the versioned ruleset as `chore:`). So every PR was re-classified by **what it actually changed to the product surface** (the `reusable-*.yml` workflows + `.github/actions/*` consumers use, and their reference docs), read from the diff. Pre-1.0 rules: `feature` → minor, `fix` → patch, breaking → minor (stay in `0.x`), and `1.0.0` is a deliberate "graduate to stable" milestone. | Version | PR | True category | Headline | |---|---|---|---| | 0.1.0 | #1 | feature | bootstrap: 3 reusable workflows | | 0.2.0 | #5 | feature | Layer-1 composite-action layer | | 0.3.0 | #6 | feature | reusable-lint bundle | | 0.4.0 | #7 | feature | reusable build-test bundle + `build` action | | 0.4.1 | #8 | fix | repoint build-test pins so the build lane resolves | | 0.5.0 | #9 | feature | reusable pkg-release workflow | | 0.6.0 | #15 | refactor **(breaking)** | rename release → pkg-release | | 0.7.0 | #16 | **feature** *(titled `ci:`)* | GO/NO GO aggregator gate + docs | | 0.8.0 | #17 | **feature** *(titled `chore:`)* | versioned canonical estate ruleset | | 1.0.0 | HEAD | milestone | graduate to stable public surface | Non-release PRs (self-host CI, ADR, dep bump, SK→A rename, skill adoptions/re-syncs, check-run rename) are folded into the body of the release window they merged in — the way a release-please changelog aggregates. ## Changes - **`changelog/`** — hand-authored dated entries (one per release) + the schema doc (`changelog/README.md`), adapted from the estate's `eslint-config`/`agent-skills` convention. - **`package.json`** — `0.0.0` → `1.0.0` (stays `private`; the version is the release anchor, not an npm publish). - **`reusable-lint.yml`** — pin the five sibling Layer-1 actions to the **`v0.8.0`** tag (`@7f543be`) instead of the bare pre-release commit, so Dependabot can track them (`v0.8.0` is the latest backfilled tag that carries every action and predates this commit, avoiding a self-reference to `v1.0.0`). - **`CLAUDE.md` / `README`** — replace the "no release tag exists yet" wording. ## After merge The git tags + GitHub releases are cut once this lands: `v0.1.0`–`v0.8.0` at their historical merge commits, `v1.0.0` at this PR's squash-merge commit, with notes sourced from each `changelog/` entry. This unblocks consumers pinning a tag (+ Dependabot) instead of a moving SHA (A-420 / A-446). ## Deferred (follow-up) - **release-please automation** + a tag-only release workflow — the forward release process is a separate decision now the backfill exists. ## Note for review > Backfilling git tags at historical commits is a deliberate divergence from the estate precedent (`eslint-config`/`agent-skills` backfilled changelog *entries* but tagged only going forward) — chosen here because A-585 explicitly wants the *full tag history* reconstructed. The pre-existing Prettier drift on the vendored `.claude`/`.agents` skill bundles is untouched (out of scope). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Marked the reusable workflows and composite actions as stable at **v1.0.0**. * Updated workflow references to use the newer version tags. * **Documentation** * Clarified release and versioning guidance across the repository. * Added and expanded changelog documentation and release notes structure. * **Chores** * Updated internal workflow pins to the latest approved release. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
What
Adds
reusable-release.yml(on: workflow_call) — the last commodity Layer-2 reusable workflow (SK-417, under SK-411). Ports the estate's hardened release flow (build-once → npm OIDC Trusted Publishing → GitHub Packages mirror → tag + GitHub release; ASW-328/326/323) into one source of truth, replacing the per-reporelease.ymlcopies that would otherwise drift (SK-384).Shape
Three least-privileged jobs:
🏗️ Build & packcontents: read🚀 Release (npm)contents/id-token/issues: writeNPM_TOKEN), version-vs-tag gate, tag + GitHub release, failure issue📦 Publish to GitHub Packagescontents: read·packages/id-token/attestations: writeDecisions
infrastructure/scripts/publish-*.shrather than calling the consumer's per-repo copies — centralises the logic and kills the SK-384 drift. Theload-repo-configoutputs the per-repo copy read becomewith:inputs.secrets:block — npm publish is OIDC, GitHub Packages +gh+ the failure issue use the automaticGITHUB_TOKEN. Only permissions cross the caller boundary.setup-project+build(pinned@d0a5949, matchingreusable-build-test.yml); publish legs hand-rolled (they needsetup-noderegistry-url/scope) and pin pnpm/Node to the same SHAs so the toolchain can't drift between build and publish.Consumer contract (documented in CLAUDE.md)
workflow_ref), not this reusable callee — each consumer registers its ownrelease.ymlcaller on npmjs.com;id-token: writeon both caller and callee.npm-releaseenvironment (absent → GitHub silently auto-creates it unprotected, losing the ASW-326 ref gate).main+cancel-in-progress: false, noworkflow_dispatch(ASW-326).Verification
run:block), yamllint, markdownlint, prettier all clean.Out of scope / follow-ups
publish-npm/publish-github-packagescomposite actions.Summary by CodeRabbit
New Features
Chores
Documentation