From 518767b939eb50bd008c0bdf9291beb11ad2df29 Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Mon, 18 May 2026 15:45:53 -0700 Subject: [PATCH 1/2] ci: fix duplicate release runs and npm-publish CI race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove push:tags trigger from release.yml — with the PAT, both release:published AND push:tags fire, causing duplicate runs. Keep only release:published + manual workflow_dispatch. - Make publish step skip already-published crates instead of failing, so re-runs are safe. - Replace instant CI status check in npm-build-publish with a wait/retry loop (30 attempts × 60s) since the release:published event fires before CI completes on the merge commit. - Update GITHUB_OPERATIONS.md to reflect automated publish flow. --- .github/workflows/npm-build-publish.yml | 29 ++++++++++++++++------- .github/workflows/release.yml | 31 ++++++++++++++----------- docs/GITHUB_OPERATIONS.md | 23 +++++++++--------- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/.github/workflows/npm-build-publish.yml b/.github/workflows/npm-build-publish.yml index 4a185af..bd290d0 100644 --- a/.github/workflows/npm-build-publish.yml +++ b/.github/workflows/npm-build-publish.yml @@ -28,24 +28,35 @@ jobs: name: verify CI passed if: github.event_name == 'release' runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 35 steps: - uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} - - name: Check CI status for commit + - name: Wait for CI to pass env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | SHA=$(git rev-parse HEAD) - echo "Checking CI status for $SHA" - STATE=$(gh api "repos/${{ github.repository }}/commits/$SHA/status" --jq '.state') - echo "Combined status: $STATE" - if [[ "$STATE" != "success" ]]; then - echo "::error::CI has not passed for $SHA (state: $STATE). Aborting publish." - exit 1 - fi + echo "Waiting for CI status on $SHA" + MAX_ATTEMPTS=30 + SLEEP_SECONDS=60 + for i in $(seq 1 $MAX_ATTEMPTS); do + STATE=$(gh api "repos/${{ github.repository }}/commits/$SHA/status" --jq '.state') + echo "Attempt $i/$MAX_ATTEMPTS: state=$STATE" + if [[ "$STATE" == "success" ]]; then + echo "CI passed." + exit 0 + elif [[ "$STATE" == "failure" || "$STATE" == "error" ]]; then + echo "::error::CI failed for $SHA (state: $STATE). Aborting publish." + exit 1 + fi + echo "CI still pending, waiting ${SLEEP_SECONDS}s..." + sleep $SLEEP_SECONDS + done + echo "::error::Timed out waiting for CI to pass ($SHA). State: $STATE" + exit 1 build-npm: name: build-npm (${{ matrix.platform }}) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1602c7..1070db6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,10 +3,16 @@ name: release # Publish to crates.io. # # Triggered by: -# - a GitHub Release published event (release-please drives this on -# merge of the release PR), or -# - a direct `git push origin v0.X.Y` tag push (emergency releases), -# - or a manual workflow_dispatch with an explicit existing tag. +# - a GitHub Release published event (release-please drives this via +# PAT, so this fires automatically on merge of the release PR), or +# - a manual workflow_dispatch with an explicit existing tag (for +# re-runs or emergency releases). +# +# Note: the `push: tags:` trigger was removed because release-please +# now uses a PAT (RELEASE_PLEASE_TOKEN) that fires both `release: +# published` AND `push: tags:` events. Keeping both caused duplicate +# runs. For emergency releases that bypass release-please, use +# workflow_dispatch. # # Stable tags (v0.1.0) mark the GitHub release as latest; pre-release # tags (v0.1.0-rc.1) mark it as prerelease so it doesn't show as @@ -19,16 +25,8 @@ name: release # consistent for downstream crates. on: - # release-please drives most releases by merging the release PR and - # publishing a GitHub Release; that fires the `release: published` - # event below. Direct `git push origin v0.X.Y` tag pushes also work - # (e.g. for emergency releases that bypass release-please). release: types: [published] - push: - tags: - - "v*.*.*-rc.*" - - "v*.*.*" workflow_dispatch: inputs: tag: @@ -147,7 +145,14 @@ jobs: publish() { local crate="$1" echo "::group::Publishing $crate" - cargo publish -p "$crate" + if ! cargo publish -p "$crate" 2>&1 | tee /tmp/publish_out; then + if grep -q "already exists on" /tmp/publish_out; then + echo "::warning::$crate already published — skipping" + else + echo "::endgroup::" + return 1 + fi + fi echo "::endgroup::" # crates.io index propagation: downstream crates' own # `cargo publish` verification step resolves their deps diff --git a/docs/GITHUB_OPERATIONS.md b/docs/GITHUB_OPERATIONS.md index 9c173e3..4a3a835 100644 --- a/docs/GITHUB_OPERATIONS.md +++ b/docs/GITHUB_OPERATIONS.md @@ -46,9 +46,9 @@ the PR. Main-branch runs always complete. This is set via the ### Release (`release.yml`) Runs on the **`release: published`** event (release-please publishes -the GitHub Release after merging the release PR), on direct **tag push** -matching `v*.*.*` (emergency releases that bypass release-please), or -via manual `workflow_dispatch` with an explicit tag input. Structure: +the GitHub Release after merging the release PR) or via manual +`workflow_dispatch` with an explicit tag input (for re-runs or +emergency releases). Structure: ``` verify ← full test suite + hyperd URL check, single-platform @@ -212,19 +212,18 @@ steps are automated based on [Conventional Commits](https://www.conventionalcomm 4. **Merge the release PR.** release-please then: - Creates a `vX.Y.Z` git tag on the merge commit. - Creates a GitHub Release with the auto-generated changelog. -5. **Wait for CI to pass** on the merge commit (the `ci.yml` workflow - runs on push to `main`). -6. **Manually trigger the publish workflows.** Because release-please - uses `GITHUB_TOKEN`, the tag push and Release events it creates do - *not* fire other workflows (a GitHub Actions limitation). Once CI is - green, run: +5. **Publish workflows fire automatically.** Because release-please + uses a PAT (`RELEASE_PLEASE_TOKEN`), the `release: published` event + triggers both `release.yml` (crates.io) and `npm-build-publish.yml` + (npm) automatically. The npm workflow waits for CI to pass before + building. + + If a publish workflow fails and needs a re-run: ```bash gh workflow run release.yml -f tag=vX.Y.Z gh workflow run npm-build-publish.yml -f tag=vX.Y.Z ``` - `release.yml` publishes the 6 Rust crates to crates.io. - `npm-build-publish.yml` builds and publishes `hyperdb-mcp` and - `hyperdb-api-node` (plus their per-platform packages) to npm. + Already-published crates are skipped gracefully on re-run. ### How commits drive version bumps From d1c2c41d5ffae68740ec9e6419e0a830df018f46 Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Mon, 18 May 2026 16:13:44 -0700 Subject: [PATCH 2/2] ci: skip release/publish workflows on forks Forks inherit workflow files but not secrets, causing spurious failures when syncing main. Add repository guard conditions so release-please, release, and npm-build-publish only run on the upstream repo. --- .github/workflows/npm-build-publish.yml | 2 +- .github/workflows/release-please.yml | 1 + .github/workflows/release.yml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/npm-build-publish.yml b/.github/workflows/npm-build-publish.yml index bd290d0..ba3721d 100644 --- a/.github/workflows/npm-build-publish.yml +++ b/.github/workflows/npm-build-publish.yml @@ -26,7 +26,7 @@ env: jobs: verify-ci: name: verify CI passed - if: github.event_name == 'release' + if: github.repository == 'tableau/hyper-api-rust' && github.event_name == 'release' runs-on: ubuntu-latest timeout-minutes: 35 steps: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 9e39a9e..277c4b7 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -28,6 +28,7 @@ permissions: jobs: release-please: + if: github.repository == 'tableau/hyper-api-rust' runs-on: ubuntu-latest steps: - uses: googleapis/release-please-action@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1070db6..89f49da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,6 +43,7 @@ env: jobs: verify: + if: github.repository == 'tableau/hyper-api-rust' name: verify runs-on: ubuntu-latest timeout-minutes: 60