Repository navigation
Enhance the release process - #909
Conversation
🤖 GitHub commentsJust comment with:
|
There was a problem hiding this comment.
🟡 Changes recommended
Critical release-path issues remain unresolved, including signing credentials, checkout pinning, publication guards, and plugin detection.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Automates Android releases through preparation and publication pull requests, replacing the manual release flow.
Changes:
- Adds release preparation, publication, dry-run, and release-note workflows.
- Adds versioning, rendering, guards, finalization, publishing, attestation, and follow-up PR tooling.
- Updates documentation and configuration while removing legacy release tooling.
File summaries
| File | Change | Review |
|---|---|---|
skills/android-release-wizard/SKILL.md |
Documents the release wizard. | Moderate (1 vote): Apostrophes in notes can break the single-quoted dispatch command; use shell-safe encoding or JSON/stdin input. |
renovate.json |
Adds dependency labels. | — |
RELEASING.md |
Rewrites release instructions. | — |
gradle.properties |
Sets the next snapshot version. | — |
catalog-info.yaml |
Updates workflow links. | — |
build.gradle.kts |
Adds the plugin listing task. | Critical (1 vote): The checked plugin ID may produce an empty list, causing a successful release without publishing; detect the appropriate plugin extension instead. |
build-tools/src/main/java/co/elastic/otel/android/compilation/tools/publishing/ApmPublisherRootPlugin.java |
Excludes skill files from formatting. | — |
.github/workflows/release.yml |
Removes the legacy release workflow. | — |
.github/workflows/release-dry-run.yml |
Adds the release dry-run workflow. | — |
.github/workflows/README.md |
Links release documentation. | — |
.github/workflows/publish-release.yml |
Adds publication and finalization workflow. | Critical (2 votes): The guard permits any merged PR targeting a releasing branch; require the matching preparation branch. |
.github/workflows/prepare-release.yml |
Prepares release branches and PRs. | Critical (1 vote): The checkout follows main instead of pinning github.sha, allowing unreviewed commits into the preparation branch. |
.github/workflows/draft-release-notes.yml |
Adds release-note drafting workflow. | — |
.github/workflows/draft-changelog.yml |
Removes the legacy changelog workflow. | — |
.github/workflows/ci.yml |
Removes the embedded release dry run. | — |
.github/scripts/release/version.sh |
Implements version arithmetic. | Moderate (1 vote): The tag glob accepts prerelease or malformed tags; filter with an anchored semantic-version expression. |
.github/scripts/release/sample.json |
Adds a release-note sample. | — |
.github/scripts/release/render-release-notes.sh |
Renders and validates release notes. | — |
.github/scripts/release/publish-guard.sh |
Guards publication. | — |
.github/scripts/release/prepare-release.sh |
Creates preparation branches and PRs. | — |
.github/scripts/release/pr-range.sh |
Resolves included pull requests. | — |
.github/scripts/release/finalize-release.sh |
Finalizes releases and opens the main PR. | Moderate (2 votes): Searching all PR states can select a closed prior PR and strand reruns; restrict to open PRs or reopen/create a replacement. |
.github/scripts/release/draft-release-notes.sh |
Drafts categorized release notes. | — |
.github/scripts/generate-release-notes/script.main.kts |
Removes the legacy note generator. | — |
.github/scripts/generate-release-notes/sample.json |
Removes the legacy sample. | — |
.github/scripts/change-log/draft.sh |
Removes the legacy changelog script. | — |
.github/scripts/bump-minor-version/main.sh |
Removes the legacy version script. | — |
.github/dependabot.yml |
Adds dependency labels. | — |
.ci/release.sh |
Reworks publishing and artifact archiving. | Critical (3 votes): Credential checks use variables not exported or consumed by the publisher, blocking Maven Central releases. Moderate (1 vote): Aggregate plugin retries cannot recover when only one of multiple Portal plugin IDs uploaded successfully. |
Review details
Suppressed comments (3)
.ci/release.sh:118
- The retry logic is project-level, but
agent-plugindeclares two Portal plugin IDs. If one ID uploads and the other fails, rerunning this aggregatepublishPluginstask attempts the already-public ID again; the Portal rejects duplicate versions before the missing ID can be retried. The marker check below only observes the partial state after the task fails, so it cannot make this recovery path work. Publish/skip individual plugin IDs or otherwise make the aggregate task resume partial projects.
if ./gradlew "$project_path:publishPlugins" \
"-Pgradle.publish.key=$PLUGIN_PORTAL_KEY" \
"-Pgradle.publish.secret=$PLUGIN_PORTAL_SECRET" \
"${common_gradle_deploy_params[@]}"; then
.github/scripts/release/version.sh:45
- This glob is not an exact
vX.Y.Zfilter:*and.also match tags such asv1.10.0-rcor malformed names. If such a tag sorts above the latest final release,parse_versionaborts instead of selecting the highest release tag, so the release workflow cannot start. Filter the tag list with an anchored semantic-version expression before selecting the highest entry.
tag=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-version:refname | head -n 1)
skills/android-release-wizard/SKILL.md:73
- Substituting the approved JSON directly into single quotes makes this command fail whenever a release-note message contains an apostrophe (for example,
user's). The skill should require shell-safe encoding/escaping of the inputs, or use a JSON/stdin form ofgh workflow run, before dispatching a release.
- Files reviewed: 30/31 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
676d62e to
69cbe6f
Compare
69cbe6f to
3507a1c
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Critical and moderate issues remain in release publication, concurrency, workflow authorization, note rendering, and dispatch handling.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
.ci/release.sh:86
- The recovery check is too coarse for a multi-module Maven publication. Each subproject registers its own
publishAndReleaseElasticToMavenCentraltask (build-tools/.../BaseApmPublisherPlugin.java:48-51), so a failed invocation can leave some artifacts public whileagent-sdkis public. This check would then skip all Maven publishing on the rerun and leave the remaining modules unreleased; verify every Maven publication (or the Central deployment state) before treating the target as complete.
# All modules go to Maven Central in one deployment that is released or
# dropped as a whole, so one module's POM tells whether this version is
# already public.
central_pom="https://repo1.maven.org/maven2/co/elastic/otel/android/agent-sdk/$release_version/agent-sdk-$release_version.pom"
.github/workflows/publish-release.yml:22
- This condition only checks the branch name, so a pull request from a fork whose branch is named
prepare/<version>can also enter this privileged job once it is merged intoreleasing/<version>. The guard receives no head-repository identity and therefore cannot distinguish that case. Requiregithub.event.pull_request.head.repo.full_name == github.repositoryhere (or pass and validate it in the guard) so release credentials and publication are only reachable from the repository's preparation branch.
&& startsWith(github.event.pull_request.base.ref, 'releasing/')
&& startsWith(github.event.pull_request.head.ref, 'prepare/')
- Files reviewed: 30/31 changed files
- Comments generated: 5
- Review effort level: Lite
3507a1c to
b1b6081
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Critical publication and release-note validation issues, plus retry and PR-range issues, remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
.github/scripts/release/pr-range.sh:68
- This exclusion list misses the legacy release branch names that are already in the repository's post-tag history. The current main history includes the merged
post-release/1.9.0PR, so the first migrated draft will emit that release-bookkeeping PR as a normal item instead of excluding it. Include the legacypost-release/*(and other oldrelease/*/pre-release/*) prefixes here, or otherwise explicitly migrate those PRs out of the range.
if [[ $head_ref == releasing/* || $head_ref == prepare/* ]]; then
continue
.github/scripts/release/prepare-release.sh:46
- This in-flight check makes preparation non-retryable after any failure that occurs after the first branch push. For example, if pushing
prepare/$release_versionor creating the PR fails afterreleasing/$release_versionwas created, a rerun sees that branch and exits here instead of reusing/finishing the same preparation. That leaves the release stranded despite the documented rerun recovery; make the branch/PR creation idempotent (or provide an explicit cleanup/recovery path).
existing_release_branches=$(git ls-remote --heads origin 'refs/heads/releasing/*' | awk '{print $2}')
if [[ -n $existing_release_branches ]]; then
echo "A release is already in flight. Finish it, or delete its branches if it was abandoned:" >&2
sed 's|refs/heads/| |' <<<"$existing_release_branches" >&2
exit 1
.github/workflows/publish-release.yml:65
- The branch and SHA values are interpolated directly into shell source. Git ref names can contain shell metacharacters, so a PR whose head ref starts with
prepare/can close the quote and execute commands in this step whileGH_TOKENis available, beforepublish-guard.shgets a chance to reject it. Pass event values throughenvand expand quoted shell variables instead of embedding them inrun.
.github/scripts/release/publish-guard.sh \
"${{ github.event.pull_request.base.ref }}" \
"${{ github.event.pull_request.head.ref }}" \
"${{ github.event.pull_request.merge_commit_sha }}"
.github/workflows/publish-release.yml:118
- The finalization command also embeds the pull request's base ref into shell source. Although the normal automation creates a numeric
releasing/x.y.zbranch, the workflow condition only checks a prefix, so a ref containing shell syntax can execute withGH_TOKENbefore the script's version check. Pass the base ref (and the other arguments) via stepenvand use quoted variables here as well.
.github/scripts/release/finalize-release.sh \
"${{ steps.guard.outputs.release_sha }}" \
"${{ steps.guard.outputs.release_version }}" \
"${{ github.event.pull_request.base.ref }}" \
"${{ steps.guard.outputs.publish_required }}"
- Files reviewed: 30/31 changed files
- Comments generated: 3
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved release automation issues affect input validation, recovery, PR-range calculation, and Plugin Portal publishing.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
.ci/release.sh:50
- These inputs are only checked for presence, not validity. A typo such as
dry_run=flaseortarget_specifier=mavenskips both publication branches, still archives artifacts, and exits 0, falsely reporting a successful release. Validate each value against the documented enum before entering the publishing logic.
if [[ $dry_run == false ]]; then
.github/scripts/release/pr-range.sh:69
- The bookkeeping filter checks only the source branch name, not its repository. A legitimate pull request from a fork whose branch is named
prepare/...orreleasing/...will therefore be omitted from the release range and its change can be missing from the release notes. Exclude these prefixes only when.head.repo.full_nameis the current repository.
head_ref=$(jq -r '.head.ref' <<<"$pull_request")
if [[ $head_ref == releasing/* || $head_ref == prepare/* ]]; then
continue
fi
- Files reviewed: 30/31 changed files
- Comments generated: 1
- Review effort level: Lite
b1b6081 to
aafa696
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Critical release-safety and shell-injection issues, plus versioning gaps, remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
.github/scripts/release/prepare-release.sh:63
- The
[Breaking]convention is documented as the signal that requires a major bump, but preparation never cross-checks it againstBUMP. A manual dispatch with a[Breaking]note andbump=minortherefore publishes a breaking change under a minor version, defeating the workflow's version-safety guard. After parsing the notes, rejectminorwhen any message starts with[Breaking](and consider rejectingmajorwithout one).
notes_file="$work_dir/release-notes.json"
rendered_file="$work_dir/release-notes.md"
printf '%s\n' "$release_notes" | jq . >"$notes_file"
"$script_dir/render-release-notes.sh" "$notes_file" "$release_version" >"$rendered_file"
.github/scripts/release/version.sh:50
- With
set -euo pipefail, a repository with no exactvX.Y.Ztags makesgrepreturn 1, so this command substitution exits the script before the explicitNo release tag matching...check below can run. Make the filter tolerate no matches sotagcan be checked and the intended error path remains reachable.
tag=$(
git tag --list 'v*' --sort=-version:refname \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
| head -n 1
RELEASING.md:6
- This new guide sends operators to
build-tools/README.mdfor publishing configuration, but that README still instructs them to setORG_GRADLE_PROJECT_signingKeyandORG_GRADLE_PROJECT_signingPassword; the publisher actually readsSECRING_ASCandKEYPASS_SECRET(seebuild-tools/.../ApmPublisherPlugin.java:41-46and.buildkite/hooks/pre-command:50-65). Update the linked documentation or remove this pointer so it does not send release operators to unusable credentials.
This guide describes how to publish EDOT Android to
[Maven Central](https://central.sonatype.com/) and the
[Gradle Plugin Portal](https://plugins.gradle.org/). For publishing
configuration details, see the [build-tools README](build-tools/README.md).
- Files reviewed: 30/31 changed files
- Comments generated: 6
- Review effort level: Lite
aafa696 to
410165f
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Critical release publication and finalization issues remain unresolved, along with legacy release-PR filtering.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
.github/scripts/release/pr-range.sh:68
- The first release prepared after this migration will still see the old workflow's
post-release/<version>PR after the latest tag: the deleted workflow created that branch to bumpgradle.propertiesonmain. This filter only excludes the newreleasing/*andprepare/*names, sodraft-release-notes.shwill present that bookkeeping PR as a release-note item (andpr-range.shwill count it as a contribution). Exclude the legacypost-release/*branch pattern during the transition.
if [[ $head_ref == releasing/* || $head_ref == prepare/* ]]; then
continue
- Files reviewed: 30/31 changed files
- Comments generated: 2
- Review effort level: Lite
410165f to
300aa84
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved critical plugin discovery and moderate release-retry and release-notes input issues can block or misexecute releases.
Review details
Suppressed comments (2)
.ci/release.sh:155
- This retries at project granularity, but
agent-plugindeclares two Portal plugins (agent-plugin/build.gradle.kts:50-65) andpublishPluginspublishes the project as one task. If that task partially uploads one plugin and then fails, the next retry can fail again on the already-public plugin before reaching the missing one; the marker check below only reports the partial state after the task. Add a per-plugin retry path (or make the publish task skip already-public IDs) so the documented partial-release recovery can finish this project.
if ./gradlew "$project_path:publishPlugins" \
"-Pgradle.publish.key=$PLUGIN_PORTAL_KEY" \
"-Pgradle.publish.secret=$PLUGIN_PORTAL_SECRET" \
"${common_gradle_deploy_params[@]}"; then
skills/android-release-wizard/SKILL.md:85
-F/--fieldis the typed-field form: withrelease_notes=@"$notes",ghreads the JSON file as a structured value, while this workflow declaresrelease_notesas a string input. The workflow dispatch can therefore reject the request or coerce the object instead of passing the JSON text toRELEASE_NOTES. Send the file contents through a raw string field (or otherwise quote the entire contents as one string).
- Files reviewed: 31/32 changed files
- Comments generated: 0 new
- Review effort level: Lite
The previous flow required typing the version, running the release workflow by hand against a release branch, and merging a separate bump pull request. Now Prepare release derives the version from the highest tag, pushes a releasing/x.y.z branch from main, and opens a prepare/x.y.z pull request into it with the release version, the release notes rendered from the authored JSON, and the regenerated NOTICE files. Merging that pull request publishes: Buildkite builds the merged commit once and publishes to Maven Central and the Gradle Plugin Portal, GitHub attests the artifacts, tags the commit, creates the GitHub Release, commits the next -SNAPSHOT version on releasing/x.y.z, and opens the release pull request into main. Nothing is merged by automation. Add the android-release-wizard skill, a draft release-notes workflow, and a release dry-run workflow; move the Buildkite dry run out of ci.yml; label dependency updates; set the development version to 1.10.0-SNAPSHOT; and replace the previous release workflows and scripts.
6a88020 to
3e31939
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
The critical plugin publication discovery and moderate legacy branch-filtering findings remain unresolved.
Review details
Suppressed comments (1)
.github/scripts/release/pr-range.sh:67
- This migration still treats the previous release PR as a release-note item: the replaced release flow created its follow-up PR from
post-release/<version>, and that PR is merged intomainafter the last release tag. Because onlyreleasing/*andprepare/*are excluded here, the first draft after this change will include the old "Prepare for the next release" bookkeeping PR (asuncategorized) instead of omitting it. Exclude the legacypost-release/*head pattern while the repository transitions to the new branch names.
if [[ $head_ref == releasing/* || $head_ref == prepare/* ]]; then
- Files reviewed: 31/32 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
The plugin publication issue and legacy bookkeeping filtering issue must be addressed.
Review details
Suppressed comments (1)
.github/scripts/release/pr-range.sh:69
- This only filters the new
releasing/*andprepare/*bookkeeping branches. The previous workflow in this PR createdpost-release/<version>(andrelease/*/pre-release/*) PRs, and the post-release version-bump PR is after the previous release tag, so the first draft will include that bookkeeping as a release change. Exclude the legacy bookkeeping branch names as well, otherwise operators must manually remove them and an otherwise empty release cannot take the no-op path.
if [[ $head_ref == releasing/* || $head_ref == prepare/* ]]; then
continue
fi
- Files reviewed: 31/32 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
Quoted from the last Copilot message
This is as intended. It's basically saying that, since the new process uses a new set of branch name patterns different from the old one, that means that it will take the last release PRs as release notes candidates (since the filter that excludes PRs for release notes only takes into account the new release branch names). However, this will only happen once (in the next release), and I can manually exclude those by then, so I don't like the idea of adding a validation for a one-off use case. |
v1v
left a comment
There was a problem hiding this comment.
I left a few comments, but didn't finish the whole review, but wanna provide a quick feedback here.
Review feedback on the release process: - .ci/release.sh treats a missing dry_run as true, so a trigger that does not ask to publish never publishes; only dry_run=false does. - finalize-release.sh documents its arguments and the environment it needs (GITHUB_REPOSITORY, GH_TOKEN, optional GITHUB_SERVER_URL and GITHUB_OUTPUT) and refuses empty or malformed arguments before creating anything: the release SHA must be a full commit SHA, the version X.Y.Z, and create-tag true or false.
RELEASING.md now describes what the android-release-wizard skill does and links directly to it. The skill now instructs the agent to ensure the local repository is current before drafting release notes.
Move the freshness check to the top of the skill so it also covers the skill and script files themselves. Let the agent pick the up-to-date main ref instead of assuming a remote named origin. Pin the gh commands and the PR lookup to elastic/apm-agent-android so a fork clone or a multi-remote checkout cannot resolve to another repository.
Keep both root build additions: the baseline Maven repository from #911 and the listPublishedGradlePlugins task the release script depends on.
Summary
Previously, the release process required some manual steps that could be further automated, such as manually forming a set of release notes and passing them to the release preparation action, and also running the release workflow manually and ensuring that the correct release branch was selected. Now, a "prepare release" action dispatch opens a preparation pull request into a
releasing/x.y.zbranch, with an additional/optional path of using an AI skill to help with writing the release notes and also helping with other arguments needed to prepare the release. Once the generated PR gets merged, the actual release is executed automatically rather than having to manually continue with the process of finding the right next GH action and running it by choosing the right branch. The automation then opens a second pull request intomainwith the release changes and the next development version. Nothing is merged by automation.What this helps with:
Changes
prepare-release.ymland its script: derive the version, render the release notes at the existing marker, regenerate the NOTICE files, pushreleasing/x.y.zandprepare/x.y.z, and open the preparation pull request.publish-release.ymlwith a guard and a finalize script: on merge intoreleasing/x.y.z, build and publish once in Buildkite from the merged commit, attest the artifacts, tag, create the GitHub Release, commit the next-SNAPSHOTversion, and open the release pull request intomain..ci/release.shto build only the merged commit, verify credentials before any upload, publish per target, and check the registries only when an upload fails so a rerun finishes a partial release; add a root Gradle task listing the published Gradle plugins.draft-release-notes.ymlandrelease-dry-run.ymlworkflows, the notes draft, render, PR range, and version scripts, theandroid-release-wizardskill, dependency labels for Renovate and Dependabot, and the1.10.0-SNAPSHOTdevelopment version; rewriteRELEASING.md.release.yml,draft-changelog.yml, the Buildkite dry run inci.yml, and the previous release scripts.