-
Notifications
You must be signed in to change notification settings - Fork 277
feat(ci): weekly scheduled patch release trigger #652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,28 +11,147 @@ on: | |
| - patch | ||
| - minor | ||
| - major | ||
| schedule: | ||
| # Weekly release, Monday 09:17 UTC. Scheduled runs fail closed if CI for | ||
| # the exact release SHA is missing or incomplete, or if the prior release | ||
| # did not reach both GitHub Releases and PyPI. A post-tag publishing failure | ||
| # requires maintainer recovery of that exact version; a later schedule will | ||
| # not skip over it. | ||
| - cron: '17 9 * * 1' | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| # Never cancel an in-flight release; queue manual and scheduled releases. | ||
| concurrency: | ||
| group: release | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| preflight: | ||
| if: github.repository == 'awslabs/cli-agent-orchestrator' | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| actions: read | ||
| contents: read | ||
| outputs: | ||
| bump: ${{ steps.policy.outputs.bump }} | ||
| count: ${{ steps.policy.outputs.count }} | ||
| last_tag: ${{ steps.policy.outputs.last_tag }} | ||
| should_release: ${{ steps.policy.outputs.should_release }} | ||
| steps: | ||
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Evaluate scheduled release policy | ||
| id: policy | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| if [ "$GITHUB_EVENT_NAME" != "schedule" ]; then | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] The early return sends all non-schedule events straight to That matters here specifically because With no required status checks on The checks are already written and only need if [ "$GITHUB_EVENT_NAME" != "schedule" ]; then
echo "bump=patch" >> "$GITHUB_OUTPUT" # inputs.bump wins downstream anyway
echo "should_release=true" >> "$GITHUB_OUTPUT"
fi
# ... then run tag resolution + CI/prior-release gates unconditionally,
# skipping only the "no new commits" early exit for manual runs. |
||
| echo "bump=patch" >> "$GITHUB_OUTPUT" | ||
| echo "count=manual" >> "$GITHUB_OUTPUT" | ||
| echo "last_tag=manual" >> "$GITHUB_OUTPUT" | ||
| echo "should_release=true" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| LAST_TAG=$( | ||
| git tag --merged "$GITHUB_SHA" --list 'v[0-9]*' --sort=-v:refname | | ||
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | | ||
| head -n 1 | ||
| ) | ||
| if [ -z "$LAST_TAG" ]; then | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Scanning Two separate things at this spot. The It still fails closed, so this is only about losing the diagnostic. The body scan is the fragile part. Today you're lucky: in A SUBJECTS=$(git log "${LAST_TAG}..${GITHUB_SHA}" --format='%s')
# footers only: last paragraph of each body
FOOTERS=$(git log "${LAST_TAG}..${GITHUB_SHA}" --format='%b' | grep -E '^BREAKING[ -]CHANGE:' || true)
if grep -Eq '^[a-z]+(\([^)]*\))?!:' <<<"$SUBJECTS" || [ -n "$FOOTERS" ]; then ...Better still, the |
||
| echo "No stable v<major>.<minor>.<patch> tag is reachable from $GITHUB_SHA" | ||
| exit 1 | ||
| fi | ||
|
|
||
| COUNT=$(git rev-list "${LAST_TAG}..${GITHUB_SHA}" --count) | ||
| echo "count=$COUNT" >> "$GITHUB_OUTPUT" | ||
| echo "last_tag=$LAST_TAG" >> "$GITHUB_OUTPUT" | ||
| if [ "$COUNT" -eq 0 ]; then | ||
| echo "No commits since $LAST_TAG; skipping this scheduled release." | ||
| echo "bump=patch" >> "$GITHUB_OUTPUT" | ||
| echo "should_release=false" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| gh release view "$LAST_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null | ||
| VERSION=${LAST_TAG#v} | ||
| python - "$VERSION" <<'PY' | ||
| import json | ||
| import sys | ||
| import urllib.error | ||
| import urllib.request | ||
|
|
||
| version = sys.argv[1] | ||
| url = f"https://pypi.org/pypi/cli-agent-orchestrator/{version}/json" | ||
| try: | ||
| with urllib.request.urlopen(url, timeout=30) as response: | ||
| json.load(response) | ||
| except (urllib.error.URLError, json.JSONDecodeError) as exc: | ||
| raise SystemExit( | ||
| f"Prior release v{version} is not verifiably published on PyPI: {exc}" | ||
| ) | ||
| PY | ||
|
|
||
| # Maintained allow-list of release-blocking workflows. Add another | ||
| # workflow file here only when release policy requires it. | ||
| REQUIRED_WORKFLOWS=(ci.yml) | ||
| for workflow in "${REQUIRED_WORKFLOWS[@]}"; do | ||
| STATE=$( | ||
| gh run list \ | ||
| --repo "$GITHUB_REPOSITORY" \ | ||
| --commit "$GITHUB_SHA" \ | ||
| --workflow "$workflow" \ | ||
| --limit 10 \ | ||
| --json conclusion,headSha,status \ | ||
| --jq 'map(select(.headSha == env.GITHUB_SHA)) | first | | ||
| if . == null then "missing" | ||
| else "\(.status):\(.conclusion // "none")" | ||
| end' | ||
| ) | ||
| if [ "$STATE" != "completed:success" ]; then | ||
| echo "$workflow is '$STATE' at $GITHUB_SHA; refusing to release" | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| COMMITS=$(git log "${LAST_TAG}..${GITHUB_SHA}" --format='%s%n%b') | ||
| if grep -Eq '(^[a-z]+(\([^)]*\))?!:|^BREAKING[ -]CHANGE:)' <<<"$COMMITS"; then | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] The next scheduled run cuts I ran this exact algorithm against the current repo state: The trigger is genuine, not a parsing bug — commit So the classification is spec-correct. The problem is the blast radius, because nothing between the cron firing and a published major release involves a person. I traced it:
Only the final This also contradicts the PR title and #651, which both say patch. I'd keep the semver derivation — that was the right fix — and just stop if grep -Eq '^feat(\([^)]*\))?:' <<<"$COMMITS"; then
BUMP=minor
else
BUMP=patch
fi
# A major is never cut unattended: surface it and let a maintainer
# run the workflow manually with bump=major.
if grep -Eq '(^[a-z]+(\([^)]*\))?!:|^BREAKING[ -]CHANGE:)' <<<"$COMMITS"; then
echo "::warning::Breaking changes since $LAST_TAG - run Release manually with bump=major"
echo "should_release=false" >> "$GITHUB_OUTPUT"
exit 0
fiAdding |
||
| BUMP=major | ||
| elif grep -Eq '^feat(\([^)]*\))?:' <<<"$COMMITS"; then | ||
| BUMP=minor | ||
| else | ||
| BUMP=patch | ||
| fi | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Require green CI on the release SHA before the bump Confirming @call-me-ram's blocker, and the live configuration makes it worse than "a human was watching the badge." I queried the repo settings: So: The ordering is what makes it unrecoverable rather than merely noisy. This job pushes the tag and creates the Release; Add a schedule-only gate here, before - name: Require green CI on the release SHA
if: github.event_name == 'schedule' && steps.unreleased.outputs.count != '0'
env:
GH_TOKEN: ${{ github.token }}
run: |
# Deliberately keyed to $GITHUB_SHA, not the branch tip: checkout pinned
# this run to github.sha, the unreleased count and the bump both ran on
# that same tree, and the final push is fast-forward-only - so there is
# no check/release race. Gating on "latest run on main" would reintroduce one.
#
# If Sunday-night CI is still in flight at 09:00 the conclusion is empty
# and this fails, skipping the week. That is the intended failure mode:
# an unverified release is worse than a late one.
conclusion=$(gh run list --commit "$GITHUB_SHA" --workflow ci.yml \
--json conclusion -q '.[0].conclusion')
if [ "$conclusion" != "success" ]; then
echo "CI is '${conclusion:-not started}' at $GITHUB_SHA - refusing to release"
exit 1
fiGiven |
||
| echo "bump=$BUMP" >> "$GITHUB_OUTPUT" | ||
| echo "should_release=true" >> "$GITHUB_OUTPUT" | ||
|
|
||
| release: | ||
| needs: preflight | ||
| if: >- | ||
| github.repository == 'awslabs/cli-agent-orchestrator' && | ||
| needs.preflight.outputs.should_release == 'true' | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 | ||
| with: | ||
| fetch-depth: 0 | ||
| ssh-key: ${{ secrets.RELEASE_DEPLOY_KEY }} | ||
|
|
||
| - name: Install git-cliff | ||
| uses: taiki-e/install-action@v2 | ||
| uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2 | ||
| with: | ||
| tool: git-cliff | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | ||
| with: | ||
| python-version: '3.12' | ||
|
|
||
|
|
@@ -41,7 +160,7 @@ jobs: | |
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| python scripts/bump_version.py ${{ inputs.bump }} | ||
| python scripts/bump_version.py ${{ inputs.bump || needs.preflight.outputs.bump }} | ||
| VERSION=$(grep '^version = ' pyproject.toml | head -1 | cut -d'"' -f2) | ||
| echo "version=$VERSION" >> $GITHUB_OUTPUT | ||
|
|
||
|
|
@@ -55,7 +174,7 @@ jobs: | |
| git push && git push --tags | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 | ||
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 | ||
| with: | ||
| tag_name: v${{ steps.bump.outputs.version }} | ||
| generate_release_notes: true | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Add a
concurrencygroup and a repository guardThere is no
concurrency:anywhere in this file (I grepped the whole thing at070f7ec). Two runs of this job can overlap the moment a cron exists — most obviously a maintainer dispatching a release around Monday 09:00.I traced what actually happens and the good news is it does not double-release: both runs check out the same SHA, both compute
2.4.2, both commit and tag locally, the loser'sgit pushis rejected non-fast-forward, and because line 75 isgit push && git push --tagsthe&&short-circuits so no stray tag is pushed and the job dies beforeCreate GitHub Release. But it is a guaranteed red run plus a confusing half-state to reason about at exactly the moment someone is trying to ship. Serialize instead:Separately, on the
releasejob:GitHub disables scheduled workflows in forks by default, so this is belt-and-braces — but note the failure mode if a fork does enable Actions:
secrets.RELEASE_DEPLOY_KEYresolves empty,actions/checkoutsilently falls back to token auth, and the job holdscontents: write, so the push succeeds and the fork tags and Releases its ownmainevery Monday. One line closes it.