Skip to content

Commit 5ff7774

Browse files
committed
fix(ci): harden release pipeline and add workflow concurrency
1 parent 5dc58a5 commit 5ff7774

9 files changed

Lines changed: 102 additions & 37 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Summary
2+
<!-- What does this PR do? -->
3+
4+
## Why
5+
<!-- Why is this change needed? -->
6+
7+
## Version impact
8+
- [ ] None
9+
- [ ] Patch
10+
- [ ] Minor
11+
- [ ] Major

.github/copilot-instructions.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ docs/ ← architecture.md, design.md, skills.md, workflow.md, roadmap
4040
```
4141

4242
*Hand-authored exceptions in `.github/`: `copilot-instructions.md`, `CODEOWNERS`,
43-
`pull_request_template.md`, `ISSUE_TEMPLATE/`, `workflows/`.
43+
`PULL_REQUEST_TEMPLATE/`, `ISSUE_TEMPLATE/`, `workflows/`.
4444

4545
Generated files are written to `.github/` at install time:
4646

@@ -132,6 +132,13 @@ For Python modules in this repository, treat code as the source of truth and kee
132132
- Default to microservices and libraries unless UI is explicitly present.
133133
- After each change: summary + files changed + how to test.
134134

135+
## Pull Request Response Format
136+
137+
- When asked to provide PR content, strictly follow the repository template at `.github/PULL_REQUEST_TEMPLATE/pull_request_template.md`.
138+
- Always provide PR content in English.
139+
- Always provide PR content as copy-paste-ready Markdown in a fenced `markdown` code block.
140+
- Do not add extra sections that are not present in the active PR template.
141+
135142
## Context Exclusions
136143

137144
When searching or reading repository content, ignore generated and third-party directories by default:

.github/pull_request_template.md

Lines changed: 0 additions & 20 deletions
This file was deleted.

.github/workflows/commit.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ on:
1313
- merge/**
1414
- gh-readonly-queue/**
1515

16+
concurrency:
17+
# Cancel superseded commit-message checks on the same branch.
18+
group: commit-${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: true
20+
1621
permissions:
1722
# Read-only is enough for commit metadata checks.
1823
contents: read

.github/workflows/qa.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ on:
88
push:
99
branches-ignore: [main]
1010

11+
concurrency:
12+
# Cancel superseded QA runs for the same branch.
13+
group: qa-${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
1116
permissions:
1217
# Workflow only needs read access to repository contents.
1318
contents: read

.github/workflows/release.yml

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ on:
88
push:
99
branches: [main]
1010

11+
concurrency:
12+
# Serialize main releases to avoid concurrent tag/release races.
13+
group: release-${{ github.ref }}
14+
cancel-in-progress: false
15+
1116
permissions:
1217
# Default to read-only; release job elevates to write for tagging/releases.
1318
contents: read
@@ -22,8 +27,7 @@ env:
2227

2328
jobs:
2429
release:
25-
# Computes semantic version from conventional commits, creates git tag,
26-
# and publishes GitHub release.
30+
# Computes semantic version from conventional commits and creates git tag.
2731
name: Compute Version and Tag
2832
runs-on: ubuntu-latest
2933
permissions:
@@ -88,26 +92,13 @@ jobs:
8892
}
8993
git config user.name "github-actions[bot]"
9094
git config user.email "github-actions[bot]@users.noreply.github.com"
91-
git rev-parse "$VERSION" >/dev/null 2>&1 && {
95+
git show-ref --verify --quiet "refs/tags/$VERSION" && {
9296
echo "ERROR: tag '$VERSION' already exists.";
9397
exit 1;
9498
}
9599
git tag -a "$VERSION" -m "release $VERSION"
96100
git push origin "$VERSION"
97101
98-
- name: Compute release date
99-
if: steps.version.outputs.changed == 'true'
100-
id: release_date
101-
run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
102-
103-
- name: Create GitHub release
104-
if: steps.version.outputs.changed == 'true'
105-
uses: softprops/action-gh-release@v3
106-
with:
107-
tag_name: ${{ steps.version.outputs.version }}
108-
name: Release v${{ steps.version.outputs.version }} (${{ steps.release_date.outputs.date }})
109-
generate_release_notes: true
110-
111102
build:
112103
# Build distributions only when semver-action reports a new release.
113104
name: Build Package Artifacts
@@ -156,6 +147,12 @@ jobs:
156147
- name: Build distributions
157148
run: poetry build
158149

150+
- name: Smoke test built wheel
151+
run: |
152+
python -m pip install --upgrade pip
153+
python -m pip install --no-deps dist/*.whl
154+
vstack --help >/dev/null
155+
159156
- name: Validate built artifact version
160157
run: |
161158
EXPECTED="${{ needs.release.outputs.version }}"
@@ -194,3 +191,47 @@ jobs:
194191

195192
- name: Publish to PyPI
196193
uses: pypa/gh-action-pypi-publish@release/v1
194+
195+
cleanup-failed-release-tag:
196+
# Delete freshly created release tag when downstream jobs fail.
197+
name: Cleanup Failed Release Tag
198+
runs-on: ubuntu-latest
199+
needs: [release, build, publish]
200+
if: ${{ always() && needs.release.outputs.changed == 'true' && (needs.build.result == 'failure' || needs.publish.result == 'failure') }}
201+
permissions:
202+
contents: write
203+
204+
steps:
205+
- name: Checkout
206+
uses: actions/checkout@v6
207+
208+
- name: Delete failed release tag
209+
run: |
210+
VERSION="${{ needs.release.outputs.version }}"
211+
if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null; then
212+
git push origin ":refs/tags/$VERSION"
213+
echo "Deleted failed release tag '$VERSION' from origin."
214+
else
215+
echo "Tag '$VERSION' already absent; nothing to delete."
216+
fi
217+
218+
github-release:
219+
# Create GitHub Release only after package build and publish succeed.
220+
name: Publish GitHub Release
221+
runs-on: ubuntu-latest
222+
needs: [release, publish]
223+
if: needs.release.outputs.changed == 'true'
224+
permissions:
225+
contents: write
226+
227+
steps:
228+
- name: Compute release date
229+
id: release_date
230+
run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
231+
232+
- name: Create GitHub release
233+
uses: softprops/action-gh-release@v3
234+
with:
235+
tag_name: ${{ needs.release.outputs.version }}
236+
name: Release v${{ needs.release.outputs.version }} (${{ steps.release_date.outputs.date }})
237+
generate_release_notes: true

.github/workflows/security.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ on:
77
pull_request:
88
branches: [main]
99

10+
concurrency:
11+
# Cancel superseded security scans for the same PR.
12+
group: security-${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
1015
permissions:
1116
# Required by checkout and PR-context scanners.
1217
contents: read

.github/workflows/verify.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ on:
77
pull_request:
88
branches: [main]
99

10+
concurrency:
11+
# Cancel superseded verification runs for the same PR.
12+
group: verify-${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
1015
permissions:
1116
# Verify jobs only read repository contents.
1217
contents: read

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ Release build versioning fix: explicit plugin activation and full history checko
88

99
- Fixed CI release builds still producing `0.0.0` artifacts by calling `poetry dynamic-versioning` explicitly before `poetry build`. Poetry reads the version once at load time — the plugin must be active and called before the build step runs.
1010
- Fixed release build tag visibility by setting `fetch-depth: 0` on the tag-pinned checkout so `git describe` can traverse full history.
11+
- Fixed release race conditions by adding workflow `concurrency` controls, including serialized main release execution.
12+
- Fixed release integrity by creating the GitHub release only after PyPI publish succeeds.
13+
- Fixed tag existence validation to check `refs/tags/<version>` directly instead of a generic ref lookup.
14+
- Fixed rerun friction after failed release attempts by automatically deleting the freshly created tag when build or publish fails.
15+
- Added post-build wheel smoke test (`pip install --no-deps` + `vstack --help`) before artifact upload to avoid network-dependent dependency resolution.
16+
- Clarified PR workflow concurrency comments in `security.yml` and `verify.yml` to match `github.ref` behavior (`refs/pull/<id>/merge`).
1117

1218
## 1.3.3 - 2026-04-22
1319

0 commit comments

Comments
 (0)