Skip to content

Commit 0853a66

Browse files
ci: fix duplicate release runs and npm-publish CI race (#24)
* ci: fix duplicate release runs and npm-publish CI race - 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. * 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.
1 parent a617f5b commit 0853a66

4 files changed

Lines changed: 52 additions & 35 deletions

File tree

‎.github/workflows/npm-build-publish.yml‎

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,37 @@ env:
2626
jobs:
2727
verify-ci:
2828
name: verify CI passed
29-
if: github.event_name == 'release'
29+
if: github.repository == 'tableau/hyper-api-rust' && github.event_name == 'release'
3030
runs-on: ubuntu-latest
31-
timeout-minutes: 5
31+
timeout-minutes: 35
3232
steps:
3333
- uses: actions/checkout@v6
3434
with:
3535
ref: ${{ github.event.release.tag_name }}
3636

37-
- name: Check CI status for commit
37+
- name: Wait for CI to pass
3838
env:
3939
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4040
run: |
4141
SHA=$(git rev-parse HEAD)
42-
echo "Checking CI status for $SHA"
43-
STATE=$(gh api "repos/${{ github.repository }}/commits/$SHA/status" --jq '.state')
44-
echo "Combined status: $STATE"
45-
if [[ "$STATE" != "success" ]]; then
46-
echo "::error::CI has not passed for $SHA (state: $STATE). Aborting publish."
47-
exit 1
48-
fi
42+
echo "Waiting for CI status on $SHA"
43+
MAX_ATTEMPTS=30
44+
SLEEP_SECONDS=60
45+
for i in $(seq 1 $MAX_ATTEMPTS); do
46+
STATE=$(gh api "repos/${{ github.repository }}/commits/$SHA/status" --jq '.state')
47+
echo "Attempt $i/$MAX_ATTEMPTS: state=$STATE"
48+
if [[ "$STATE" == "success" ]]; then
49+
echo "CI passed."
50+
exit 0
51+
elif [[ "$STATE" == "failure" || "$STATE" == "error" ]]; then
52+
echo "::error::CI failed for $SHA (state: $STATE). Aborting publish."
53+
exit 1
54+
fi
55+
echo "CI still pending, waiting ${SLEEP_SECONDS}s..."
56+
sleep $SLEEP_SECONDS
57+
done
58+
echo "::error::Timed out waiting for CI to pass ($SHA). State: $STATE"
59+
exit 1
4960
5061
build-npm:
5162
name: build-npm (${{ matrix.platform }})

‎.github/workflows/release-please.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ permissions:
2828

2929
jobs:
3030
release-please:
31+
if: github.repository == 'tableau/hyper-api-rust'
3132
runs-on: ubuntu-latest
3233
steps:
3334
- uses: googleapis/release-please-action@v5

‎.github/workflows/release.yml‎

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ name: release
33
# Publish to crates.io.
44
#
55
# Triggered by:
6-
# - a GitHub Release published event (release-please drives this on
7-
# merge of the release PR), or
8-
# - a direct `git push origin v0.X.Y` tag push (emergency releases),
9-
# - or a manual workflow_dispatch with an explicit existing tag.
6+
# - a GitHub Release published event (release-please drives this via
7+
# PAT, so this fires automatically on merge of the release PR), or
8+
# - a manual workflow_dispatch with an explicit existing tag (for
9+
# re-runs or emergency releases).
10+
#
11+
# Note: the `push: tags:` trigger was removed because release-please
12+
# now uses a PAT (RELEASE_PLEASE_TOKEN) that fires both `release:
13+
# published` AND `push: tags:` events. Keeping both caused duplicate
14+
# runs. For emergency releases that bypass release-please, use
15+
# workflow_dispatch.
1016
#
1117
# Stable tags (v0.1.0) mark the GitHub release as latest; pre-release
1218
# tags (v0.1.0-rc.1) mark it as prerelease so it doesn't show as
@@ -19,16 +25,8 @@ name: release
1925
# consistent for downstream crates.
2026

2127
on:
22-
# release-please drives most releases by merging the release PR and
23-
# publishing a GitHub Release; that fires the `release: published`
24-
# event below. Direct `git push origin v0.X.Y` tag pushes also work
25-
# (e.g. for emergency releases that bypass release-please).
2628
release:
2729
types: [published]
28-
push:
29-
tags:
30-
- "v*.*.*-rc.*"
31-
- "v*.*.*"
3230
workflow_dispatch:
3331
inputs:
3432
tag:
@@ -45,6 +43,7 @@ env:
4543

4644
jobs:
4745
verify:
46+
if: github.repository == 'tableau/hyper-api-rust'
4847
name: verify
4948
runs-on: ubuntu-latest
5049
timeout-minutes: 60
@@ -147,7 +146,14 @@ jobs:
147146
publish() {
148147
local crate="$1"
149148
echo "::group::Publishing $crate"
150-
cargo publish -p "$crate"
149+
if ! cargo publish -p "$crate" 2>&1 | tee /tmp/publish_out; then
150+
if grep -q "already exists on" /tmp/publish_out; then
151+
echo "::warning::$crate already published — skipping"
152+
else
153+
echo "::endgroup::"
154+
return 1
155+
fi
156+
fi
151157
echo "::endgroup::"
152158
# crates.io index propagation: downstream crates' own
153159
# `cargo publish` verification step resolves their deps

‎docs/GITHUB_OPERATIONS.md‎

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ the PR. Main-branch runs always complete. This is set via the
4646
### Release (`release.yml`)
4747

4848
Runs on the **`release: published`** event (release-please publishes
49-
the GitHub Release after merging the release PR), on direct **tag push**
50-
matching `v*.*.*` (emergency releases that bypass release-please), or
51-
via manual `workflow_dispatch` with an explicit tag input. Structure:
49+
the GitHub Release after merging the release PR) or via manual
50+
`workflow_dispatch` with an explicit tag input (for re-runs or
51+
emergency releases). Structure:
5252

5353
```
5454
verify ← full test suite + hyperd URL check, single-platform
@@ -212,19 +212,18 @@ steps are automated based on [Conventional Commits](https://www.conventionalcomm
212212
4. **Merge the release PR.** release-please then:
213213
- Creates a `vX.Y.Z` git tag on the merge commit.
214214
- Creates a GitHub Release with the auto-generated changelog.
215-
5. **Wait for CI to pass** on the merge commit (the `ci.yml` workflow
216-
runs on push to `main`).
217-
6. **Manually trigger the publish workflows.** Because release-please
218-
uses `GITHUB_TOKEN`, the tag push and Release events it creates do
219-
*not* fire other workflows (a GitHub Actions limitation). Once CI is
220-
green, run:
215+
5. **Publish workflows fire automatically.** Because release-please
216+
uses a PAT (`RELEASE_PLEASE_TOKEN`), the `release: published` event
217+
triggers both `release.yml` (crates.io) and `npm-build-publish.yml`
218+
(npm) automatically. The npm workflow waits for CI to pass before
219+
building.
220+
221+
If a publish workflow fails and needs a re-run:
221222
```bash
222223
gh workflow run release.yml -f tag=vX.Y.Z
223224
gh workflow run npm-build-publish.yml -f tag=vX.Y.Z
224225
```
225-
`release.yml` publishes the 6 Rust crates to crates.io.
226-
`npm-build-publish.yml` builds and publishes `hyperdb-mcp` and
227-
`hyperdb-api-node` (plus their per-platform packages) to npm.
226+
Already-published crates are skipped gracefully on re-run.
228227

229228
### How commits drive version bumps
230229

0 commit comments

Comments
 (0)