Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 29 additions & 52 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,16 +377,14 @@ jobs:
--title "$RELEASE_TAG"

bump_main:
name: Start next patch development
name: Open next patch development pull request
needs:
- central_publish
- github_release
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
snapshot_commit: ${{ steps.bump.outputs.commit || steps.recovered-bump.outputs.commit }}
snapshot_required: ${{ steps.bump.outputs.snapshot_required || steps.recovered-bump.outputs.snapshot_required }}
pull-requests: write

steps:
- name: Checkout repository
Expand Down Expand Up @@ -438,17 +436,32 @@ jobs:
;;
esac

- name: Commit and push next development version
id: bump
- name: Create next development version pull request
if: steps.next-version.outputs.should_push == 'true'
shell: bash
env:
NEXT_VERSION: ${{ steps.next-version.outputs.version }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

branch="release/start-$NEXT_VERSION"
existing_pr="$(
gh pr list \
--base main \
--head "$branch" \
--state open \
--json url \
--jq '.[0].url'
)"
if [[ -n "$existing_pr" ]]; then
echo "::notice::Version-bump pull request already exists: $existing_pr"
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "$branch"
git add -- gradle.properties

mapfile -t staged_files < <(git diff --cached --name-only)
Expand All @@ -458,49 +471,13 @@ jobs:
fi

git commit -m "chore: start $NEXT_VERSION development"
bump_commit="$(git rev-parse HEAD)"
if ! git push origin HEAD:main; then
if ! remote_ref="$(git ls-remote --exit-code origin refs/heads/main)"; then
echo "::error::Version-bump push failed and remote main could not be resolved"
exit 1
fi
remote_main_sha="$(awk '$2 == "refs/heads/main" { print $1; exit }' <<< "$remote_ref")"
if [[ "$remote_main_sha" != "$bump_commit" ]]; then
echo "::error::Unable to push the version bump to main. Main may have advanced, or branch protection may reject github-actions[bot]."
exit 1
fi
echo "::notice::Push reported failure, but remote main contains the exact version-bump commit; continuing recovery"
fi
echo "commit=$bump_commit" >> "$GITHUB_OUTPUT"
echo "snapshot_required=true" >> "$GITHUB_OUTPUT"

- name: Recover an already-completed version bump
id: recovered-bump
if: steps.next-version.outputs.status == 'equal'
shell: bash
run: |
set -euo pipefail
bump_commit="$(git rev-parse HEAD)"
if ! remote_ref="$(git ls-remote --exit-code origin refs/heads/main)"; then
echo "::error::Unable to resolve remote main while recovering the version bump"
exit 1
fi
remote_main_sha="$(awk '$2 == "refs/heads/main" { print $1; exit }' <<< "$remote_ref")"
if [[ "$remote_main_sha" != "$bump_commit" ]]; then
echo "::error::Checked-out main changed while recovering the version bump"
exit 1
fi
echo "commit=$bump_commit" >> "$GITHUB_OUTPUT"
echo "snapshot_required=true" >> "$GITHUB_OUTPUT"

publish_next_snapshot:
name: Publish next development snapshot
needs: bump_main
if: needs.bump_main.outputs.snapshot_required == 'true'
uses: ./.github/workflows/publish-snapshot.yml
with:
commit: ${{ needs.bump_main.outputs.snapshot_commit }}
fail_if_stale: true
secrets:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
git push origin "HEAD:refs/heads/$branch"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Orphaned Branch Blocks Recovery

If this push succeeds but gh pr create fails, the remote branch remains without an open PR. A rerun creates a new local commit with a different identity, and this push is rejected as non-fast-forward, so the version-bump PR cannot be opened without manually deleting or reconciling the branch. The same failure occurs when a closed PR leaves its branch behind.

Fix in Cursor


pr_url="$(
gh pr create \
--base main \
--head "$branch" \
--title "chore: start $NEXT_VERSION development" \
--body "Automated post-release version bump. This PR changes only \`gradle.properties\`."
Comment on lines +476 to +481

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Token-Created PR Skips Checks

This command creates the PR with the repository GITHUB_TOKEN. GitHub does not start new workflow runs for events produced by that token, so the Build workflow's pull_request checks will not run for this PR; if those checks are required by branch protection, the automated version bump cannot be merged.

Fix in Cursor

)"
echo "::notice::Opened version-bump pull request: $pr_url"
25 changes: 12 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,7 @@ rechecks remote `main`, and publishes using the same files:

It reads `MAVEN_CENTRAL_USERNAME` and `MAVEN_CENTRAL_PASSWORD` from repository or organization
secrets. Pull requests, pushes to other branches, failed builds, and stale `main` pushes do not
publish. The Release workflow also calls this reusable workflow with the exact version-bump commit,
because pushes made with `GITHUB_TOKEN` do not start another Build workflow.
publish.

### Release spec inputs

Expand Down Expand Up @@ -355,22 +354,21 @@ protected `maven-central` environment, the workflow runs in this order:
7. Create the tag's GitHub Release with generated notes. If a published release exists, leave it
unchanged; if a draft exists, publish that draft without replacing its title, notes, or assets.
8. Read the latest `main`, calculate the next patch version (for example,
`1.2.3` → `1.2.4-SNAPSHOT`), and commit only `gradle.properties` directly to `main`.
9. Pass the exact version-bump commit to the reusable Publish Snapshot workflow, which builds and
publishes the new development snapshot from frozen OpenAPI inputs.
`1.2.3` → `1.2.4-SNAPSHOT`), then create a pull request containing only
`gradle.properties`.
9. A maintainer merges that pull request through normal branch protection. The Build workflow runs
for the resulting `main` push and publishes the new development snapshot.

If a version-bump push reports an ambiguous failure, the workflow checks whether remote `main`
contains the exact local commit before failing. A rerun that finds the expected next version already
on `main` recovers that commit and requests snapshot publication again.
The version-bump branch name is derived from the target snapshot version. A rerun reuses an
existing open pull request instead of creating a duplicate.

Do not queue multiple release dispatches. With `cancel-in-progress: false`, GitHub retains at most
one running and one pending run in the release concurrency group; a newer dispatch can replace an
older pending run.

The workflow defaults to `contents: read`; only the GitHub Release and version-bump jobs receive
`contents: write`. Branch protection or repository rulesets must allow `github-actions[bot]` to
push the direct version-bump commit to `main`; otherwise Central and the GitHub Release can succeed
while the final bump fails.
The workflow defaults to `contents: read`; the GitHub Release job receives `contents: write`, and
the version-bump job receives `contents: write` plus `pull-requests: write` to create its
non-protected branch and pull request. Branch protection remains enforced for the merge to `main`.

### Recovery and partial failures

Expand All @@ -389,7 +387,8 @@ ambiguous NMCP failure:
- If only the GitHub Release or version-bump job failed after Central publication succeeded, use
GitHub Actions' **Re-run failed jobs**. This does not rerun the successful Central publication
job. GitHub Release creation and the version bump are designed to tolerate an already-completed
result; an existing draft release is published in place.
result; an existing draft release is published in place, and an existing open version-bump pull
request is reused.

Never enable recovery merely because a run failed. First verify that the exact release is public on
Maven Central; recovery refuses missing, unexpected, or mismatched POMs.
Expand Down
61 changes: 61 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,67 @@ being closed for missing information.)

This repo uses merge commits, so there is no need to squash commits before opening a PR.

## Maintainer releases

Stable releases are performed through GitHub. Create the tag and GitHub Release in the GitHub UI,
then use the GitHub Actions **Release** workflow to publish the artifacts to Maven Central.

Before starting, ensure the intended release commit and the current Release workflow are on
`main`. You must also be allowed to create release tags by the repository ruleset. If GitHub
rejects tag creation with `GH013` and “creations being restricted,” ask a repository administrator
to allow release-tag creation or add you to the ruleset bypass list.

### 1. Create the tag and GitHub Release

In GitHub, open **Releases → Draft a new release**:

1. In **Choose a tag**, enter a stable version in the exact form `vMAJOR.MINOR.PATCH`, such as
`v1.2.3`, then select **Create new tag**.
2. Set the target branch to `main`.
3. Enter the release title and notes.
4. Select **Publish release**.

Publishing the GitHub Release creates the remote tag. The tag must point to a commit reachable
from `main`.

### 2. Run the GitHub Release workflow

In GitHub, open **Actions → Release → Run workflow**:

1. Select the `main` branch.
2. Set `tag` to the tag created with the GitHub Release, such as `v1.2.3`.
3. Leave `recover_existing_release` disabled for a normal release.
4. Run the workflow.

### 3. Approve Maven Central publication

Approve the protected `maven-central` environment deployment when GitHub requests approval.

The workflow validates the tag, builds and signs the artifacts, publishes to Maven Central,
leaves the already-published GitHub Release unchanged, and opens a pull request for the next
patch `-SNAPSHOT` version.

### 4. Merge the next-development-version pull request

After the release workflow opens its version-bump pull request, approve and merge it through the
normal `main` branch-protection process. The Build workflow then runs for the resulting `main`
commit and publishes the next development snapshot.

### 5. Verify the release

Confirm that the GitHub Actions workflow completed successfully and that the release POM is
available from Maven Central:

```text
https://repo.maven.apache.org/maven2/markets/alpaca/alpaca-java/1.2.3/alpaca-java-1.2.3.pom
```

### Recovery after an ambiguous Maven Central result

Enable `recover_existing_release` only after confirming the exact release POM is publicly
available from Maven Central following an ambiguous publication failure. Never use recovery for a
new release or merely because a workflow run failed.

## Coding Guidelines

### Generated vs handwritten code
Expand Down
Loading