refactor(ci): release and publish release workflows #87
Conversation
Branch protection on main blocks direct pushes from the release workflow (it requires PR, semgrep, CodeQL). Split the single release job into two files so the version bump goes through the same gates as every other change. - release.yml: dispatch-triggered. Tests, builds, bumps the version on a release/<pkg>-v<ver> branch, opens a PR with a 'release' label, and enables auto-merge (squash). Auto-merge fires once required checks + code-owner review pass. - release-publish.yml: triggers on the merged release PR. Detects which package changed, builds, tags <pkg>/v<ver>, and publishes to npm with provenance. Tag step is idempotent so retried runs don't fail on an existing tag. A workflow_dispatch input is provided as an escape hatch for manually re-running publish for a specific package after a fix. Supporting fixes needed for the publish to actually succeed: - .yarnrc.yml: set npmPublishRegistry to registry.npmjs.org. Yarn Berry otherwise PUTs to registry.yarnpkg.com (a read-only mirror) and gets a 404. - packages/*/package.json: add a repository field. Required by npm's provenance verification, which cross-checks the package.json against the OIDC-supplied repo URL and rejects with 422 if the field is empty or missing.
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR refactors the release process from a single-step workflow into a two-stage pipeline. The release.yml workflow now opens a release PR with version bumps and auto-merges it, while a new release-publish.yml workflow handles publishing merged packages to npm. Supporting npm and package configuration is added across yarnrc and package.json files. ChangesRelease Automation Refactoring
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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/release-publish.yml:
- Line 48: The checkout ref for manual dispatch uses github.ref which can point
to any branch; update the conditional that sets ref (the expression using
github.event_name == 'pull_request' &&
github.event.pull_request.merge_commit_sha || github.ref) so that when
github.event_name == 'workflow_dispatch' it uses the fixed ref 'refs/heads/main'
instead of github.ref, preserving the existing behavior for pull_request merges;
adjust the conditional expression accordingly so manual dispatch always checks
out main.
In @.github/workflows/release.yml:
- Around line 93-101: The "Create release branch" step currently always POSTs a
git ref and fails on retries; update the script that uses GH_TOKEN, BRANCH and
SHA (the gh api calls) to first check for the ref's existence (gh api GET
/repos/.../git/refs/heads/$BRANCH) and only POST the new ref if the GET returns
not found, or alternately catch a 422 from the POST and treat it as success;
modify the logic around the gh api calls in that step so branch creation is
idempotent on workflow reruns.
🪄 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: CHILL
Plan: Pro
Run ID: f7593053-eb45-49fe-aca7-ffc7d41aa47f
📒 Files selected for processing (6)
.github/workflows/release-publish.yml.github/workflows/release.yml.yarnrc.ymlpackages/builder/package.jsonpackages/cli/package.jsonpackages/simulator/package.json
| - name: Create release branch | ||
| env: | ||
| GH_TOKEN: ${{ steps.gh-app-token.outputs.token }} | ||
| BRANCH: ${{ steps.version.outputs.branch }} | ||
| run: | | ||
| SHA=$(gh api "/repos/${{ github.repository }}/git/refs/heads/${{ github.ref_name }}" -q .object.sha) | ||
| gh api --method POST "/repos/${{ github.repository }}/git/refs" \ | ||
| -f ref="refs/heads/$BRANCH" \ | ||
| -f sha="$SHA" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and read the release.yml file
find . -path "./.git" -prune -o -name "release.yml" -type f -printRepository: OpenZeppelin/compact-tools
Length of output: 102
🏁 Script executed:
# Read the release.yml file to see the actual code
cat -n .github/workflows/release.yml | head -120Repository: OpenZeppelin/compact-tools
Length of output: 4883
🏁 Script executed:
# Check if there are any error handling or try-catch patterns in the workflow
rg "git/refs" .github/workflows/ -A 3 -B 3Repository: OpenZeppelin/compact-tools
Length of output: 695
🏁 Script executed:
# Check if there's any documentation or other workflows using similar patterns
rg "git/refs" --type yaml -B 5 -A 5Repository: OpenZeppelin/compact-tools
Length of output: 52
🌐 Web query:
GitHub API POST /repos/{owner}/{repo}/git/refs error handling duplicate ref
💡 Result:
When using the GitHub REST API endpoint POST /repos/{owner}/{repo}/git/refs to create a reference, attempting to create a reference that already exists will result in a 422 Unprocessable Entity error [1][2]. In this scenario, the API response body typically includes a message indicating that the reference update failed, often accompanied by a documentation URL [3]. It is important to ensure that the ref parameter provided in your request body is fully qualified (e.g., refs/heads/my-branch-name or refs/tags/my-tag-name) [1][2][4]. If the provided ref is not fully qualified or is malformed, the API may also return a 422 error [1][3]. To handle this in your application: 1. Check for the 422 status code [1][2]. 2. Parse the error message to confirm the failure is due to a duplicate reference or validation issue [3]. 3. If the goal is to update an existing reference rather than create a new one, you should use the PATCH /repos/{owner}/{repo}/git/refs/{ref} endpoint instead [5]. Note that a 409 Conflict error may also occur if the repository is empty or currently being initialized, which is distinct from a duplicate reference error [5].
Citations:
- 1: https://docs.github.com/en/rest/git/refs
- 2: https://docs.github.com/rest/git/refs
- 3: https://gist.github.com/jasonrudolph/10727108
- 4: https://stackoverflow.com/questions/9506181/github-api-create-branch
- 5: https://docs.github.com/en/rest/guides/using-the-rest-api-to-interact-with-your-git-database?apiVersion=2026-03-10
🌐 Web query:
gh cli GitHub API ref endpoint error handling 404 422
💡 Result:
In the GitHub CLI (gh), the gh api command handles HTTP errors by printing the error message to stderr and exiting with a non-zero status code by default [1][2]. Because error handling is tightly coupled with output formatting, users have limited control over these responses [1][3]. Handling 404 and 422 Errors: 404 Not Found: This error indicates that the requested resource could not be found [4][5]. In the context of gh api, common causes include: - Incorrect URL paths or typos [5]. Notably, adding a leading slash (/) to an endpoint path can sometimes cause issues in specific shells or configurations [6][7]. - Accessing a private resource without proper authentication [5]. GitHub returns a 404 rather than a 403 for private resources to prevent confirming their existence [5]. - Missing required OAuth scopes (e.g., workflow scope), which can sometimes manifest as a 404 for certain operations depending on the underlying git objects [8]. 422 Unprocessable Entity: This error signifies that the request was syntactically correct but could not be processed due to validation failures [5]. - Common causes include missing required parameters, incorrect parameter types, or invalid field formatting [5]. - The response body typically contains an errors property with a code field (e.g., missing, missing_field, invalid, already_exists) to help diagnose the issue [5]. Best Practices for Error Handling: - Command Scripting: When scripting with gh api, use the || true pattern or check exit codes to prevent the entire script from failing immediately upon an API error [3]. - Debugging: Use the --verbose flag with gh api to inspect the full HTTP request and response, which is essential for diagnosing why a 404 or 422 is occurring [2]. - Output Control: Note that gh api may return raw JSON error responses to stdout if you are using filters (like -q or -t), which can complicate parsing [3]. Ensure your scripts are prepared to handle or ignore these error outputs [3]. - Tool Maturity: Be aware that gh api error handling is a known area for improvement in the GitHub CLI project [1][9][3]. Complex scripts may require manual status code checks or parsing of the response body to provide granular, user-friendly feedback [7][10].
Citations:
- 1: The
gh apicommand needs some love cli/cli#10558 - 2: https://cli.github.com/manual/gh_api
- 3: gh api: give the user control over HTTP error responses cli/cli#5209
- 4: https://www.reddit.com/r/github/comments/15ffcnf/github_cli_api_404_message/
- 5: https://docs.github.com/en/rest/overview/troubleshooting
- 6: https://stackoverflow.com/questions/73011381/why-reading-pull-request-data-using-github-cli-api-causes-gh-not-found-http-4
- 7: https://stackoverflow.com/questions/74944954/bash-script-is-returning-gh-not-found-http-404-error-writing-output-failed
- 8:
gh release createworks for some commit hashes but gets weird 404 for others cli/cli#9773 - 9: Unclear Error Handling for Status-Only API Response cli/cli#4734
- 10: https://dev.to/bala_paranj_059d338e44e7e/error-handling-in-a-go-cli-that-security-researchers-trust-2jj3
Make release-branch creation idempotent for reruns.
Line 99 always creates the ref unconditionally. If the workflow is retried after branch creation succeeds, this step fails with a 422 error and blocks recovery.
🔧 Proposed fix
- name: Create release branch
env:
GH_TOKEN: ${{ steps.gh-app-token.outputs.token }}
BRANCH: ${{ steps.version.outputs.branch }}
run: |
- SHA=$(gh api "/repos/${{ github.repository }}/git/refs/heads/${{ github.ref_name }}" -q .object.sha)
- gh api --method POST "/repos/${{ github.repository }}/git/refs" \
- -f ref="refs/heads/$BRANCH" \
- -f sha="$SHA"
+ if gh api "/repos/${{ github.repository }}/git/refs/heads/$BRANCH" >/dev/null 2>&1; then
+ echo "branch $BRANCH already exists; reusing it"
+ else
+ SHA=$(gh api "/repos/${{ github.repository }}/git/refs/heads/${{ github.ref_name }}" -q .object.sha)
+ gh api --method POST "/repos/${{ github.repository }}/git/refs" \
+ -f ref="refs/heads/$BRANCH" \
+ -f sha="$SHA"
+ fi🧰 Tools
🪛 zizmor (1.25.2)
[error] 98-98: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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/release.yml around lines 93 - 101, The "Create release
branch" step currently always POSTs a git ref and fails on retries; update the
script that uses GH_TOKEN, BRANCH and SHA (the gh api calls) to first check for
the ref's existence (gh api GET /repos/.../git/refs/heads/$BRANCH) and only POST
the new ref if the GET returns not found, or alternately catch a 422 from the
POST and treat it as success; modify the logic around the gh api calls in that
step so branch creation is idempotent on workflow reruns.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| - name: Create release branch | ||
| env: | ||
| GH_TOKEN: ${{ steps.gh-app-token.outputs.token }} | ||
| BRANCH: ${{ steps.version.outputs.branch }} | ||
| run: | | ||
| SHA=$(gh api "/repos/${{ github.repository }}/git/refs/heads/${{ github.ref_name }}" -q .object.sha) | ||
| gh api --method POST "/repos/${{ github.repository }}/git/refs" \ | ||
| -f ref="refs/heads/$BRANCH" \ | ||
| -f sha="$SHA" |
- release-publish.yml: pin manual dispatch checkout to refs/heads/main instead of github.ref, so workflow_dispatch from a non-main branch can't publish from an unintended ref. - release.yml: make 'Create release branch' idempotent — skip the POST if the branch already exists, so retried dispatches don't 422 on a partial previous run.
Reverts the refs/heads/main pin from the previous commit. Publishing from a hotfix or release-prep branch is a legitimate use case, and the compact-npm-prod environment approval is the actual security gate (approving a release is the trust boundary, not the branch ref).
Changes:
mainblocks the current release workflow. it tries to push the version bump directly, but the ruleset requires PR + semgrep + CodeQL.release.yml(dispatch) — tests, builds, bumps version on arelease/<pkg>-v<ver>branch, opens a PR with areleaselabel, enables squash auto-merge.release-publish.yml(PR-closed/merged) — detects which package changed, builds, tags<pkg>/v<ver>, publishes to npm with provenance.workflow_dispatchescape hatch onrelease-publish.yml— manually re-publish a specific package after a fix without bumping the version..yarnrc.yml: setnpmPublishRegistrytoregistry.npmjs.org: Yarn Berry was PUTting to the read-onlyregistry.yarnpkg.commirror and getting 404s on publish.packages/*/package.json: addrepositoryfield, required by npm provenance verification (rejects with 422 when missing).