From 16c7c95743caa7a9239c654e0af9df90dcedb0b4 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 17 Mar 2026 13:36:59 -0500 Subject: [PATCH 1/5] fix(repo): use Promise.allSettled for release downstream dispatches --- .github/workflows/release.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed68f7eae79..cf950cd8b3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,7 +109,11 @@ jobs: ref: 'main', }), ]; - await Promise.all(dispatches); + const results = await Promise.allSettled(dispatches); + const failed = results.filter(r => r.status === 'rejected'); + if (failed.length) { + failed.forEach(r => core.warning(`Dispatch failed: ${r.reason?.message ?? r.reason}`)); + } } else{ core.warning("Changeset in pre-mode should not prepare a ClerkJS production release") } @@ -232,7 +236,11 @@ jobs: ); } - await Promise.all(dispatches); + const results = await Promise.allSettled(dispatches); + const failed = results.filter(r => r.status === 'rejected'); + if (failed.length) { + failed.forEach(r => core.warning(`Dispatch failed: ${r.reason?.message ?? r.reason}`)); + } - name: Notify Slack on failure if: ${{ always() && steps.publish.outcome == 'failure' }} From 71cb67b55c42a78fb5ad07c3d2db85374582c284 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 17 Mar 2026 13:39:18 -0500 Subject: [PATCH 2/5] fix(repo): fail dispatch step without blocking release job --- .github/workflows/release.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf950cd8b3b..ff2d836f252 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,6 +74,7 @@ jobs: - name: Trigger workflows on related repos if: steps.changesets.outputs.published == 'true' + continue-on-error: true uses: actions/github-script@v7 with: result-encoding: string @@ -112,7 +113,8 @@ jobs: const results = await Promise.allSettled(dispatches); const failed = results.filter(r => r.status === 'rejected'); if (failed.length) { - failed.forEach(r => core.warning(`Dispatch failed: ${r.reason?.message ?? r.reason}`)); + failed.forEach(r => core.error(`Dispatch failed: ${r.reason?.message ?? r.reason}`)); + core.setFailed(`${failed.length} downstream dispatch(es) failed`); } } else{ core.warning("Changeset in pre-mode should not prepare a ClerkJS production release") @@ -202,6 +204,7 @@ jobs: - name: Trigger workflows on related repos if: steps.publish.outcome == 'success' + continue-on-error: true uses: actions/github-script@v7 with: result-encoding: string @@ -239,7 +242,8 @@ jobs: const results = await Promise.allSettled(dispatches); const failed = results.filter(r => r.status === 'rejected'); if (failed.length) { - failed.forEach(r => core.warning(`Dispatch failed: ${r.reason?.message ?? r.reason}`)); + failed.forEach(r => core.error(`Dispatch failed: ${r.reason?.message ?? r.reason}`)); + core.setFailed(`${failed.length} downstream dispatch(es) failed`); } - name: Notify Slack on failure From 91bf9f6c6d643eb8328147e473a74c5a57273cfa Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 17 Mar 2026 13:49:14 -0500 Subject: [PATCH 3/5] fix(repo): include target repo in dispatch error messages --- .github/workflows/release.yml | 73 +++++++++++++---------------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff2d836f252..a97e2543eef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,33 +88,20 @@ jobs: const clerkUiVersion = require('./packages/ui/package.json').version; const nextjsVersion = require('./packages/nextjs/package.json').version; - const dispatches = [ - 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', - }), + const targets = [ + { repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } }, + { repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } }, + { repo: 'clerk-docs', workflow_id: 'typedoc.yml' }, ]; - const results = await Promise.allSettled(dispatches); - const failed = results.filter(r => r.status === 'rejected'); - if (failed.length) { - failed.forEach(r => core.error(`Dispatch failed: ${r.reason?.message ?? r.reason}`)); - core.setFailed(`${failed.length} downstream dispatch(es) failed`); + const results = await Promise.allSettled( + targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t })) + ); + const failures = results + .map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null) + .filter(Boolean); + if (failures.length) { + failures.forEach(f => core.error(`Dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`)); + core.setFailed(`${failures.length} downstream dispatch(es) failed`); } } else{ core.warning("Changeset in pre-mode should not prepare a ClerkJS production release") @@ -216,34 +203,26 @@ jobs: const clerkUiVersion = require('./packages/ui/package.json').version; const nextjsVersion = require('./packages/nextjs/package.json').version; - 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 } - }), + const targets = [ + { repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', 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 } - }), + targets.push( + { repo: 'accounts', workflow_id: 'release-staging.yml', inputs: { version: nextjsVersion } }, ); } - const results = await Promise.allSettled(dispatches); - const failed = results.filter(r => r.status === 'rejected'); - if (failed.length) { - failed.forEach(r => core.error(`Dispatch failed: ${r.reason?.message ?? r.reason}`)); - core.setFailed(`${failed.length} downstream dispatch(es) failed`); + const results = await Promise.allSettled( + targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t })) + ); + const failures = results + .map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null) + .filter(Boolean); + if (failures.length) { + failures.forEach(f => core.error(`Dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`)); + core.setFailed(`${failures.length} downstream dispatch(es) failed`); } - name: Notify Slack on failure From 9bfaa801af65fe99434bcd8167d32f7c93ee2180 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 17 Mar 2026 13:56:17 -0500 Subject: [PATCH 4/5] chore: add empty changeset --- .changeset/release-dispatch-allsettled.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/release-dispatch-allsettled.md diff --git a/.changeset/release-dispatch-allsettled.md b/.changeset/release-dispatch-allsettled.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/release-dispatch-allsettled.md @@ -0,0 +1,2 @@ +--- +--- From 32f1622e738ada432ffac601ea46736de9701393 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 17 Mar 2026 14:24:15 -0500 Subject: [PATCH 5/5] fix(repo): add recovery step for downstream notifications --- .github/workflows/release.yml | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a97e2543eef..e536c4f4f1d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -107,6 +107,85 @@ jobs: core.warning("Changeset in pre-mode should not prepare a ClerkJS production release") } + # Recovery: if the changesets action published to npm but then failed + # (e.g. git push --follow-tags error), the `published` output is never + # set and downstream repos are not notified. This step detects that + # scenario by checking npm for the local package version and dispatches + # if the packages are already live. + - name: Recover downstream notifications + if: always() && steps.changesets.conclusion == 'failure' + continue-on-error: true + 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 { execSync } = require('child_process'); + + const clerkjsVersion = require('./packages/clerk-js/package.json').version; + const clerkUiVersion = require('./packages/ui/package.json').version; + + // Only recover stable releases + const preReleases = [ + clerkjsVersion.includes('-') && `@clerk/clerk-js@${clerkjsVersion}`, + clerkUiVersion.includes('-') && `@clerk/ui@${clerkUiVersion}`, + ].filter(Boolean); + if (preReleases.length > 0) { + console.log(`Skipping recovery: ${preReleases.join(', ')} is a pre-release`); + return; + } + + const preMode = require("fs").existsSync("./.changeset/pre.json"); + if (preMode) { + core.warning("Changeset in pre-mode, skipping recovery dispatch"); + return; + } + + // Check if either version was actually published to npm + function isPublished(name, version) { + try { + return execSync(`npm view ${name}@${version} version`, { encoding: 'utf8' }).trim() === version; + } catch { + return false; + } + } + + const clerkjsPublished = isPublished('@clerk/clerk-js', clerkjsVersion); + const clerkUiPublished = isPublished('@clerk/ui', clerkUiVersion); + + if (!clerkjsPublished && !clerkUiPublished) { + console.log('Neither @clerk/clerk-js nor @clerk/ui were published to npm, no recovery needed'); + return; + } + + const published = [ + clerkjsPublished && `@clerk/clerk-js@${clerkjsVersion}`, + clerkUiPublished && `@clerk/ui@${clerkUiVersion}`, + ].filter(Boolean).join(', '); + core.warning(`Recovery: ${published} published to npm but downstream repos were not notified. Dispatching now.`); + + const nextjsVersion = require('./packages/nextjs/package.json').version; + + const targets = [ + { repo: 'sdk-infra-workers', workflow_id: 'update-pkg-versions.yml', inputs: { clerkjsVersion, clerkUiVersion } }, + { repo: 'dashboard', workflow_id: 'prepare-nextjs-sdk-update.yml', inputs: { version: nextjsVersion } }, + { repo: 'clerk-docs', workflow_id: 'typedoc.yml' }, + ]; + const results = await Promise.allSettled( + targets.map(t => github.rest.actions.createWorkflowDispatch({ owner: 'clerk', ref: 'main', ...t })) + ); + const failures = results + .map((r, i) => r.status === 'rejected' ? { target: targets[i], reason: r.reason } : null) + .filter(Boolean); + if (failures.length) { + failures.forEach(f => core.error(`Recovery dispatch to ${f.target.repo}/${f.target.workflow_id} failed: ${f.reason?.message ?? f.reason}`)); + core.setFailed(`${failures.length} recovery dispatch(es) failed`); + } else { + core.notice('Recovery dispatch completed successfully'); + } + - name: Generate notification payload id: notification if: steps.changesets.outputs.published == 'true'