Skip to content

Commit e79b3d7

Browse files
kurodo3[bot]claude
authored andcommitted
fix: defer tag push until after build; update docs for merged release workflow
release.yml: split "Tag release" into two steps — create the tag locally first (so hatch-vcs can derive the version during uv build), then push to origin only after a successful build. The push step also fails fast with a clear error if the tag already exists on origin (e.g. workflow re-run for the same version). Adds a prominent comment on publish-testpypi noting that PyPI/TestPyPI Trusted Publisher configs must reference release.yml (not the former publish.yml). spec/plan docs: update all references to the old two-workflow model (tag-only release.yml + tag-triggered publish.yml, GitHub App token) to reflect the merged release.yml that runs tests, builds, publishes to TestPyPI→PyPI, and creates the GitHub Release using GITHUB_TOKEN only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2e46084 commit e79b3d7

3 files changed

Lines changed: 43 additions & 44 deletions

File tree

.github/workflows/release.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,24 +61,42 @@ jobs:
6161
git config user.name "github-actions[bot]"
6262
git config user.email "github-actions[bot]@users.noreply.github.com"
6363
64-
- name: Tag release
65-
run: |
66-
git tag "v${{ steps.normalize.outputs.version }}"
67-
git push origin "v${{ steps.normalize.outputs.version }}"
64+
# Create the tag locally so hatch-vcs can derive the package version during
65+
# `uv build`. The tag is pushed to origin only AFTER a successful build to
66+
# avoid leaving a dangling remote tag if the build fails.
67+
- name: Create local release tag
68+
run: git tag "v${{ steps.normalize.outputs.version }}"
6869

6970
- name: Install uv
7071
uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
7172

7273
- name: Build wheel and sdist
7374
run: uv build
7475

76+
# Fail fast if the tag already exists on origin (e.g. re-running the workflow
77+
# for the same version) before attempting to push, to surface a clear error
78+
# rather than a confusing "already exists" git message.
79+
- name: Push release tag
80+
run: |
81+
if git ls-remote --exit-code --tags origin \
82+
"refs/tags/v${{ steps.normalize.outputs.version }}" > /dev/null 2>&1; then
83+
echo "ERROR: tag v${{ steps.normalize.outputs.version }} already exists on origin" >&2
84+
exit 1
85+
fi
86+
git push origin "v${{ steps.normalize.outputs.version }}"
87+
7588
- name: Upload dist artifact
7689
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
7790
with:
7891
name: dist
7992
path: dist/
8093
if-no-files-found: error
8194

95+
# IMPORTANT: The PyPI and TestPyPI Trusted Publisher configurations must reference
96+
# this workflow file (`.github/workflows/release.yml`). If they still point to the
97+
# former `publish.yml`, OIDC token exchange will be rejected by PyPI/TestPyPI.
98+
# Update both configs at https://pypi.org and https://test.pypi.org before running
99+
# this workflow for the first time.
82100
publish-testpypi:
83101
name: Publish → TestPyPI
84102
needs: build

docs/metamorphic/plans/2026-06-18-golden-vector-parity.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,9 +1092,15 @@ jobs:
10921092
git push origin v${{ inputs.version }}
10931093
```
10941094

1095-
Note: the tag push triggers `publish.yml` which runs tests, builds the wheel and sdist via `uv build`, and publishes to TestPyPI then PyPI using OIDC Trusted Publishing.
1095+
Note: the merged `release.yml` handles the full pipeline — tests (matrix), build,
1096+
TestPyPI → PyPI publish (OIDC Trusted Publishing), and GitHub Release creation. No
1097+
GitHub App token is required; `GITHUB_TOKEN` with `contents: write` handles the tag
1098+
push. The PyPI/TestPyPI Trusted Publisher configuration must reference `release.yml`
1099+
(not the former `publish.yml`).
10961100

1097-
Required secrets: `RELEASE_APP_ID`, `RELEASE_APP_PRIVATE_KEY` (GitHub App with `contents:write` on `nauticalab/starfix-python`).
1101+
Required secrets: `STARFIX_APP_ID` + `STARFIX_APP_PRIVATE_KEY` (for `golden-sync-check`
1102+
in `ci.yml`). The `RELEASE_APP_ID` / `RELEASE_APP_PRIVATE_KEY` secrets are no longer
1103+
needed by `release.yml`.
10981104

10991105
- [ ] **Step 2: Commit**
11001106

@@ -1124,9 +1130,9 @@ gh pr create \
11241130
- Adds `tests/golden/include_metadata_v0.3.json` (copied from authoritative Rust fixture)
11251131
- Adds `tests/test_golden_parity_metadata.py`: 9 parametrized cross-language parity tests + 2 invariant tests
11261132
- Adds `golden-sync-check` CI job to `ci.yml` — fetches fixture from `nauticalab/starfix` main on every PR and fails on drift
1127-
- Adds manual `release.yml` workflow using GitHub App token
1133+
- Adds manual `release.yml` workflow: tests → build → TestPyPI → PyPI → GitHub Release (OIDC, no API token secrets needed)
11281134

1129-
Requires `STARFIX_APP_ID` + `STARFIX_APP_PRIVATE_KEY` secrets (for drift check) and `RELEASE_APP_ID` + `RELEASE_APP_PRIVATE_KEY` secrets (for release workflow) to be set on the repo.
1135+
Requires `STARFIX_APP_ID` + `STARFIX_APP_PRIVATE_KEY` secrets (for `golden-sync-check` drift check) to be set on the repo. Also requires the PyPI/TestPyPI Trusted Publisher configs to reference `.github/workflows/release.yml`.
11301136

11311137
Part of PLT-1735. Companion PR: nauticalab/starfix (Rust side).
11321138

docs/metamorphic/specs/2026-06-18-golden-vector-parity-design.md

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -326,47 +326,22 @@ Required secrets: `RELEASE_APP_ID`, `RELEASE_APP_PRIVATE_KEY` — a GitHub App w
326326
1. `cargo-release` bumps `Cargo.toml` → commits → publishes crate to crates.io → creates `v{version}` tag → pushes both
327327
2. Tag push fires `ci.yml` → runs tests and verifies version/tag sync
328328

329-
#### `nauticalab/starfix-python` — new `.github/workflows/release.yml`
329+
#### `nauticalab/starfix-python` — `.github/workflows/release.yml`
330330

331331
`hatch-vcs` reads the version from git tags automatically; there is no version file to
332-
bump. The release workflow only needs to create and push the tag:
332+
bump. The release workflow handles the full pipeline: tests, build, TestPyPI → PyPI
333+
publish, and GitHub Release creation. No GitHub App token is needed — `GITHUB_TOKEN`
334+
with `contents: write` is sufficient for the tag push.
333335

334-
```yaml
335-
name: release
336-
on:
337-
workflow_dispatch:
338-
inputs:
339-
version:
340-
description: 'Release version (e.g. 0.3.0)'
341-
required: true
342-
type: string
343-
jobs:
344-
release:
345-
runs-on: ubuntu-latest
346-
steps:
347-
- name: Generate GitHub App token
348-
id: app-token
349-
uses: actions/create-github-app-token@v3
350-
with:
351-
app-id: ${{ secrets.RELEASE_APP_ID }}
352-
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
353-
354-
- uses: actions/checkout@v4
355-
with:
356-
token: ${{ steps.app-token.outputs.token }}
336+
The tag is created locally first (so `hatch-vcs` can derive the version during `uv build`),
337+
then pushed to origin only after a successful build to avoid leaving a dangling remote tag
338+
on build failure.
357339

358-
- name: Configure git
359-
run: |
360-
git config user.name "github-actions[bot]"
361-
git config user.email "github-actions[bot]@users.noreply.github.com"
362-
363-
- name: Tag and push release
364-
run: |
365-
git tag v${{ inputs.version }}
366-
git push origin v${{ inputs.version }}
367-
```
340+
Job sequence: `test` (matrix 3.10/3.11/3.12) → `build` (local tag + build + push tag) →
341+
`publish-testpypi` → `publish-pypi` (PyPI publish + GitHub Release).
368342

369-
Tag push fires the existing `publish.yml` → pure-Python package published to PyPI.
343+
> **Note:** The PyPI and TestPyPI Trusted Publisher configurations must reference
344+
> `.github/workflows/release.yml`. Update both configs before triggering the first release.
370345

371346
### Release procedure (coordinated across both repos)
372347

0 commit comments

Comments
 (0)