Skip to content

[build] create a github release draft before running publish#17422

Open
titusfortner wants to merge 5 commits intotrunkfrom
split_gh_release
Open

[build] create a github release draft before running publish#17422
titusfortner wants to merge 5 commits intotrunkfrom
split_gh_release

Conversation

@titusfortner
Copy link
Copy Markdown
Member

💥 What does this PR do?

The github release action is what we're using to create the tag, so if anything in the publish job breaks, the github release isn't prepped and it's harder to recover.

🤖 AI assistance

  • No substantial AI assistance used
  • AI assisted (complete below)
    • Tool(s): Claude
    • What was generated:
    • I reviewed all AI output and can explain the change

Copilot AI review requested due to automatic review settings May 7, 2026 15:39
@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Split GitHub release into draft and publish stages for better recovery

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Split GitHub release workflow into draft and publish stages
• Create draft release before publish job to improve failure recovery
• Move nightly release cleanup to draft stage for better separation
• Update job dependencies and conditions for new workflow structure

Grey Divider

File Changes

1. .github/workflows/release.yml ✨ Enhancement +45/-38

Refactor release workflow into separate draft and publish stages

• Added new github-release-draft job that creates a draft release before publishing
• Moved nightly release deletion logic from github-release to github-release-draft
• Renamed github-release job to github-release-publish and updated its dependencies
• Updated job conditions and dependencies throughout workflow to reference new job names
• Simplified github-release-publish by removing draft creation and moving it to separate stage

.github/workflows/release.yml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented May 7, 2026

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (2)

Grey Divider


Action required

1. Non-idempotent tag creation 🐞 Bug ☼ Reliability ⭐ New
Description
create-language-tag unconditionally POSTs a new refs/tags/${TAG} without checking if it already
exists, so workflow_dispatch runs against an existing tag (or reruns after partial progress) can
fail before docs/other steps that require the tag.
Code

.github/workflows/release.yml[R70-78]

+      - name: Create language-specific tag
+        env:
+          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
+          TAG: ${{ needs.parse-tag.outputs.tag }}
+          SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
+        run: |
+          gh api -X POST /repos/${{ github.repository }}/git/refs \
+            -f ref="refs/tags/${TAG}" \
+            -f sha="${SHA}"
Evidence
The workflow accepts an arbitrary tag via workflow_dispatch and only validates the tag *format*
(not existence). For patch releases (language != 'all'), the workflow then always tries to create
the tag via the GitHub refs API with no existence check or delete/recreate behavior, making the
pipeline brittle for reruns or pre-created tags.

.github/workflows/release.yml[6-11]
.github/workflows/parse-release-tag.yml[39-43]
.github/workflows/release.yml[62-78]
.github/workflows/release.yml[205-224]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`create-language-tag` always attempts to create `refs/tags/${TAG}` via `gh api -X POST .../git/refs`. This is not idempotent: if the tag already exists (manual pre-tagging, workflow_dispatch reruns, or reruns after partial success), the step fails and blocks downstream jobs.

### Issue Context
- Patch releases (`language != 'all'`) require a language-suffixed tag.
- Downstream docs uses this tag as the checkout ref.

### Fix Focus Areas
- .github/workflows/release.yml[62-78]

### Suggested fix approach
- Add a pre-check for the tag ref:
 - `gh api /repos/${{ github.repository }}/git/ref/tags/${TAG}`
- If it exists:
 - If it already points to `${SHA}`, treat as success and exit 0.
 - Otherwise fail with a clear error (or optionally delete/recreate if that matches your release policy).
- If it does not exist, create it (current POST).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Draft failure bypasses docs gate 🐞 Bug ≡ Correctness ⭐ New
Description
For full releases, if github-release-draft fails, github-release-publish is skipped due to the
!failure() guard; docs treats "skipped" as acceptable (!= 'failure') and can proceed even
though the release tag may not exist, causing checkout failures in the docs workflow.
Code

.github/workflows/release.yml[R207-213]

+    needs: [parse-tag, publish, publish-python, github-release-publish, create-language-tag]
    if: >-
      always() && !cancelled() &&
      needs.publish.result == 'success' &&
      (needs.publish-python.result == 'success' || needs.publish-python.result == 'skipped') &&
-      needs.github-release.result != 'failure'
+      needs.github-release-publish.result != 'failure' &&
+      needs.create-language-tag.result != 'failure'
Evidence
The full-release tag is created in github-release-draft via ncipollo/release-action using `tag:
needs.parse-tag.outputs.tag. If that job fails, github-release-publish` will not run (it requires
!failure()), resulting in a job conclusion of "skipped". The docs job only blocks on
needs.github-release-publish.result != 'failure', so it can run while the tag was never created.
The docs workflow ultimately checks out inputs.tag (passed from needs.parse-tag.outputs.tag) via
the shared bazel.yml checkout, which requires the tag to exist.

.github/workflows/release.yml[80-102]
.github/workflows/release.yml[155-163]
.github/workflows/release.yml[205-224]
.github/workflows/update-documentation.yml[72-83]
.github/workflows/bazel.yml[130-135]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
On full releases (`language == 'all'`), a failure in `github-release-draft` can still allow `docs` to run because `github-release-publish` becomes "skipped" (due to `!failure()`), and `docs` only checks `needs.github-release-publish.result != 'failure'`. This can lead to docs trying to checkout a tag that was never created.

### Issue Context
- Tag creation for full releases happens in `github-release-draft`.
- Docs generation checks out `inputs.tag`.

### Fix Focus Areas
- .github/workflows/release.yml[155-163]
- .github/workflows/release.yml[205-214]

### Suggested fix approach
Option A (recommended): explicitly require draft success for full releases
- Add `github-release-draft` to `docs.needs`.
- Update the `docs.if` condition to include something like:
 - `(needs.parse-tag.outputs.language != 'all' || needs.github-release-draft.result == 'success')`

Option B: make `github-release-publish` fail (not skip) when draft fails
- Adjust `github-release-publish.if` to avoid converting a draft failure into a skipped job (e.g., remove `!failure()` and instead explicitly gate on `needs.publish.result == 'success'`), so downstream checks can reliably block on failure.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Release publish skipped 🐞 Bug ≡ Correctness
Description
github-release-publish needs github-release-draft, but github-release-draft is skipped unless the
parsed language is 'all', so java/dotnet patch releases will skip github-release-publish and never
upload artifacts / publish the release.
Code

.github/workflows/release.yml[R62-97]

+  github-release-draft:
+    name: GitHub Release Draft
+    needs: [parse-tag, get-approval]
+    if: needs.parse-tag.outputs.language == 'all'
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v4
+        with:
+          persist-credentials: false
+      - name: Delete nightly release and tag
+        env:
+          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
+        run: |
+          if gh release view nightly >/dev/null 2>&1; then
+            gh release delete nightly --yes
+          fi
+          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
+            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
+          fi
+      - name: Create draft GitHub release
+        uses: ncipollo/release-action@v1
+        with:
+          draft: true
+          allowUpdates: true
+          bodyFile: "scripts/github-actions/release_header.md"
+          generateReleaseNotes: true
+          name: "Selenium ${{ needs.parse-tag.outputs.version }}"
+          tag: "${{ needs.parse-tag.outputs.tag }}"
+          commit: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
+
  publish:
    name: Build and Publish ${{ matrix.language }}
    needs: [parse-tag, get-approval]
Evidence
The workflow sets github-release-draft to run only for full releases (language == 'all'), but
github-release-publish declares it as a hard dependency while still intending to run for java/dotnet
releases. This repo already encodes the workaround pattern (use always() on the dependent job) in
restrict-trunk.yml when the upstream job may be skipped, indicating the current release.yml wiring
will skip downstream jobs in this scenario.

.github/workflows/release.yml[62-66]
.github/workflows/release.yml[115-123]
.github/workflows/parse-release-tag.yml[56-73]
.github/workflows/restrict-trunk.yml[34-49]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`github-release-publish` depends on `github-release-draft`, but `github-release-draft` is skipped for non-`all` releases. This causes `github-release-publish` to be skipped for java/dotnet patch releases, preventing GitHub release uploads.

### Issue Context
- `parse-release-tag.yml` sets `language` to the tag suffix for patch releases (e.g., `java`, `dotnet`).
- The repo already uses the pattern `if: always() && ...` for jobs that need a conditionally-skipped job (`restrict-trunk.yml`).

### Fix Focus Areas
- .github/workflows/release.yml[62-66]
- .github/workflows/release.yml[115-123]

### Suggested fix approach
Pick one (A is usually simplest):

**A) Make `github-release-draft` never be skipped, and gate its *steps* instead**
- Remove the job-level `if:` from `github-release-draft`.
- Add `if: needs.parse-tag.outputs.language == 'all'` onto the draft-only steps (checkout, delete nightly, create draft release).
- Keep `github-release-publish` depending on it.

**B) Keep `github-release-draft` skippable, but make `github-release-publish` resilient**
- Change `github-release-publish` job condition to `if: always() && !cancelled() && needs.publish.result == 'success' && (...)`.
- Add an extra guard so for `language == 'all'` you require `needs.github-release-draft.result == 'success'`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Missing artifact download path 🐞 Bug ☼ Reliability
Description
For non-'all' releases, the artifact download step does not set a download path but the upload
command reads from build/dist/*.*, which can result in no matching files and a failed GitHub upload.
Code

.github/workflows/release.yml[R136-146]

        uses: actions/download-artifact@v4
        with:
          name: release-packages-${{ needs.parse-tag.outputs.language }}
-      - name: Delete nightly release and tag
-        if: needs.parse-tag.outputs.language == 'all'
-        env:
-          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
-        run: |
-          if gh release view nightly >/dev/null 2>&1; then
-            gh release delete nightly --yes
-          fi
-          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
-            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
-          fi
-      - name: Create GitHub release
-        if: needs.parse-tag.outputs.language == 'all'
-        uses: ncipollo/release-action@v1
-        with:
-          allowUpdates: true
-          artifacts: "build/dist/*.*"
-          bodyFile: "scripts/github-actions/release_header.md"
-          generateReleaseNotes: true
-          name: "Selenium ${{ needs.parse-tag.outputs.version }}"
-          tag: "${{ needs.parse-tag.outputs.tag }}"
-          commit: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
-      - name: Update GitHub release
-        if: needs.parse-tag.outputs.language != 'all'
+      - name: Upload to GitHub release
        env:
          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
          VERSION: ${{ needs.parse-tag.outputs.version }}
        run: |
          BASE_VERSION="${VERSION%.*}.0"
          gh release upload "selenium-$BASE_VERSION" build/dist/*.* --clobber
+          gh release edit "selenium-$BASE_VERSION" --draft=false
Evidence
This workflow uploads artifacts from build/dist/*.* in the reusable Bazel workflow, and other
workflows in this repo (nightly) explicitly download the artifact into build/dist before
referencing build/dist/*.*. In release.yml, the non-'all' download omits path: build/dist/ but
still uploads from build/dist/*.*, making the file location inconsistent between the download and
upload steps.

.github/workflows/release.yml[127-146]
.github/workflows/bazel.yml[283-290]
.github/workflows/nightly.yml[89-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
In `github-release-publish`, the non-`all` artifact download does not specify `path: build/dist/`, but the later `gh release upload` reads `build/dist/*.*`. If the artifact extracts to the workspace root (or another default directory), the upload glob won’t match anything and the job fails.

### Issue Context
This repo’s nightly workflow downloads the same kind of artifact into `build/dist` before using `build/dist/*.*`, indicating `path: build/dist` is the expected convention.

### Fix Focus Areas
- .github/workflows/release.yml[134-146]

### Suggested fix approach
Update the non-`all` download step to match the `all` path:
- Add `path: "build/dist/"` under `actions/download-artifact@v4` for the `name: release-packages-${{ needs.parse-tag.outputs.language }}` case.
- Optionally also set `merge-multiple: true` if you expect multiple artifacts in that path (not required for single-name downloads).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Unquoted args in gh 📘 Rule violation ☼ Reliability ⭐ New
Description
The workflow introduces shell commands with unquoted arguments/globs (e.g., API endpoint and
build/dist/*.*), which can break on unexpected characters or empty matches and reduces
determinism/safety in CI scripting. This violates the requirement to harden scripts with safe
argument handling.
Code

.github/workflows/release.yml[R76-78]

+          gh api -X POST /repos/${{ github.repository }}/git/refs \
+            -f ref="refs/tags/${TAG}" \
+            -f sha="${SHA}"
Evidence
PR Compliance ID 10 requires safe argument handling in build/CI scripts (quoting variables/paths and
using robust patterns). The added gh api call passes an unquoted endpoint argument, and the new
release upload step passes an unguarded glob (build/dist/*.*), both of which are explicitly within
the workflow run scripts added/changed in this PR.

.github/workflows/release.yml[76-78]
.github/workflows/release.yml[200-203]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Workflow `run` steps use less-robust argument handling (unquoted endpoint argument and unguarded glob expansion), which can lead to brittle CI behavior.

## Issue Context
PR Compliance ID 10 expects safe quoting/patterns in CI scripts.

## Fix Focus Areas
- .github/workflows/release.yml[76-78]
- .github/workflows/release.yml[200-203]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Release deletion errors suppressed 📘 Rule violation ☼ Reliability
Description
The new workflow suppresses all errors when checking for the nightly release/tag, which can mask
real failures (e.g., auth/network) and make CI outcomes non-deterministic. This conflicts with the
requirement to avoid error swallowing in build/CI scripts.
Code

.github/workflows/release.yml[R78-83]

+          if gh release view nightly >/dev/null 2>&1; then
+            gh release delete nightly --yes
+          fi
+          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
+            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
+          fi
Evidence
PR Compliance ID 13 requires CI scripts to avoid swallowing errors that mask failures. The added `gh
release view nightly >/dev/null 2>&1 check discards all stderr/stdout, and the git ls-remote ... |
grep -q ...` pipeline is used only as a boolean guard, so errors in these commands can be hidden
instead of failing the job.

.github/workflows/release.yml[78-83]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow suppresses errors while probing for the `nightly` release/tag (e.g., `>/dev/null 2>&1`), which can hide real failures (bad token, rate limiting, network issues) and lead to non-deterministic CI behavior.

## Issue Context
Compliance requires CI scripts to avoid swallowing errors that mask failures.

## Fix Focus Areas
- .github/workflows/release.yml[78-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Nightly deleted too early 🐞 Bug ☼ Reliability
Description
The nightly release and tag are now deleted in github-release-draft, which runs before/parallel to
the publish matrix, so a publish failure can still leave nightly deleted even though the release
never completed.
Code

.github/workflows/release.yml[R74-83]

+      - name: Delete nightly release and tag
+        env:
+          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
+        run: |
+          if gh release view nightly >/dev/null 2>&1; then
+            gh release delete nightly --yes
+          fi
+          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
+            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
+          fi
Evidence
github-release-draft deletes the nightly release/tag but does not depend on the publish job, so this
cleanup can execute even if publish later fails. The diff shows this cleanup was moved out of the
post-publish GitHub release job into the pre-publish draft job.

.github/workflows/release.yml[62-83]
.github/workflows/release.yml[95-113]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Nightly release/tag deletion currently happens before publish completes. If publish fails, nightly may remain deleted even though the release did not successfully publish.

### Issue Context
The goal of the PR is to create the GitHub release draft early; nightly cleanup does not need to happen early and is safer after publish success.

### Fix Focus Areas
- .github/workflows/release.yml[74-83]
- .github/workflows/release.yml[115-147]

### Suggested fix approach
- Move the "Delete nightly release and tag" step from `github-release-draft` into `github-release-publish` (or another post-publish job).
- Gate it with publish success (e.g., keep `github-release-publish` running only when `needs.publish.result == 'success'`) so nightly is only deleted when the release is actually going out.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 4724ab8

Results up to commit 5c40a80


🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0)


Action required
1. Release publish skipped 🐞 Bug ≡ Correctness
Description
github-release-publish needs github-release-draft, but github-release-draft is skipped unless the
parsed language is 'all', so java/dotnet patch releases will skip github-release-publish and never
upload artifacts / publish the release.
Code

.github/workflows/release.yml[R62-97]

+  github-release-draft:
+    name: GitHub Release Draft
+    needs: [parse-tag, get-approval]
+    if: needs.parse-tag.outputs.language == 'all'
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v4
+        with:
+          persist-credentials: false
+      - name: Delete nightly release and tag
+        env:
+          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
+        run: |
+          if gh release view nightly >/dev/null 2>&1; then
+            gh release delete nightly --yes
+          fi
+          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
+            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
+          fi
+      - name: Create draft GitHub release
+        uses: ncipollo/release-action@v1
+        with:
+          draft: true
+          allowUpdates: true
+          bodyFile: "scripts/github-actions/release_header.md"
+          generateReleaseNotes: true
+          name: "Selenium ${{ needs.parse-tag.outputs.version }}"
+          tag: "${{ needs.parse-tag.outputs.tag }}"
+          commit: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
+
  publish:
    name: Build and Publish ${{ matrix.language }}
    needs: [parse-tag, get-approval]
Evidence
The workflow sets github-release-draft to run only for full releases (language == 'all'), but
github-release-publish declares it as a hard dependency while still intending to run for java/dotnet
releases. This repo already encodes the workaround pattern (use always() on the dependent job) in
restrict-trunk.yml when the upstream job may be skipped, indicating the current release.yml wiring
will skip downstream jobs in this scenario.

.github/workflows/release.yml[62-66]
.github/workflows/release.yml[115-123]
.github/workflows/parse-release-tag.yml[56-73]
.github/workflows/restrict-trunk.yml[34-49]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`github-release-publish` depends on `github-release-draft`, but `github-release-draft` is skipped for non-`all` releases. This causes `github-release-publish` to be skipped for java/dotnet patch releases, preventing GitHub release uploads.

### Issue Context
- `parse-release-tag.yml` sets `language` to the tag suffix for patch releases (e.g., `java`, `dotnet`).
- The repo already uses the pattern `if: always() && ...` for jobs that need a conditionally-skipped job (`restrict-trunk.yml`).

### Fix Focus Areas
- .github/workflows/release.yml[62-66]
- .github/workflows/release.yml[115-123]

### Suggested fix approach
Pick one (A is usually simplest):

**A) Make `github-release-draft` never be skipped, and gate its *steps* instead**
- Remove the job-level `if:` from `github-release-draft`.
- Add `if: needs.parse-tag.outputs.language == 'all'` onto the draft-only steps (checkout, delete nightly, create draft release).
- Keep `github-release-publish` depending on it.

**B) Keep `github-release-draft` skippable, but make `github-release-publish` resilient**
- Change `github-release-publish` job condition to `if: always() && !cancelled() && needs.publish.result == 'success' && (...)`.
- Add an extra guard so for `language == 'all'` you require `needs.github-release-draft.result == 'success'`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Missing artifact download path 🐞 Bug ☼ Reliability
Description
For non-'all' releases, the artifact download step does not set a download path but the upload
command reads from build/dist/*.*, which can result in no matching files and a failed GitHub upload.
Code

.github/workflows/release.yml[R136-146]

        uses: actions/download-artifact@v4
        with:
          name: release-packages-${{ needs.parse-tag.outputs.language }}
-      - name: Delete nightly release and tag
-        if: needs.parse-tag.outputs.language == 'all'
-        env:
-          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
-        run: |
-          if gh release view nightly >/dev/null 2>&1; then
-            gh release delete nightly --yes
-          fi
-          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
-            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
-          fi
-      - name: Create GitHub release
-        if: needs.parse-tag.outputs.language == 'all'
-        uses: ncipollo/release-action@v1
-        with:
-          allowUpdates: true
-          artifacts: "build/dist/*.*"
-          bodyFile: "scripts/github-actions/release_header.md"
-          generateReleaseNotes: true
-          name: "Selenium ${{ needs.parse-tag.outputs.version }}"
-          tag: "${{ needs.parse-tag.outputs.tag }}"
-          commit: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
-      - name: Update GitHub release
-        if: needs.parse-tag.outputs.language != 'all'
+      - name: Upload to GitHub release
        env:
          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
          VERSION: ${{ needs.parse-tag.outputs.version }}
        run: |
          BASE_VERSION="${VERSION%.*}.0"
          gh release upload "selenium-$BASE_VERSION" build/dist/*.* --clobber
+          gh release edit "selenium-$BASE_VERSION" --draft=false
Evidence
This workflow uploads artifacts from build/dist/*.* in the reusable Bazel workflow, and other
workflows in this repo (nightly) explicitly download the artifact into build/dist before
referencing build/dist/*.*. In release.yml, the non-'all' download omits path: build/dist/ but
still uploads from build/dist/*.*, making the file location inconsistent between the download and
upload steps.

.github/workflows/release.yml[127-146]
.github/workflows/bazel.yml[283-290]
.github/workflows/nightly.yml[89-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
In `github-release-publish`, the non-`all` artifact download does not specify `path: build/dist/`, but the later `gh release upload` reads `build/dist/*.*`. If the artifact extracts to the workspace root (or another default directory), the upload glob won’t match anything and the job fails.

### Issue Context
This repo’s nightly workflow downloads the same kind of artifact into `build/dist` before using `build/dist/*.*`, indicating `path: build/dist` is the expected convention.

### Fix Focus Areas
- .github/workflows/release.yml[134-146]

### Suggested fix approach
Update the non-`all` download step to match the `all` path:
- Add `path: "build/dist/"` under `actions/download-artifact@v4` for the `name: release-packages-${{ needs.parse-tag.outputs.language }}` case.
- Optionally also set `merge-multiple: true` if you expect multiple artifacts in that path (not required for single-name downloads).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
3. Release deletion errors suppressed 📘 Rule violation ☼ Reliability
Description
The new workflow suppresses all errors when checking for the nightly release/tag, which can mask
real failures (e.g., auth/network) and make CI outcomes non-deterministic. This conflicts with the
requirement to avoid error swallowing in build/CI scripts.
Code

.github/workflows/release.yml[R78-83]

+          if gh release view nightly >/dev/null 2>&1; then
+            gh release delete nightly --yes
+          fi
+          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
+            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
+          fi
Evidence
PR Compliance ID 13 requires CI scripts to avoid swallowing errors that mask failures. The added `gh
release view nightly >/dev/null 2>&1 check discards all stderr/stdout, and the git ls-remote ... |
grep -q ...` pipeline is used only as a boolean guard, so errors in these commands can be hidden
instead of failing the job.

.github/workflows/release.yml[78-83]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow suppresses errors while probing for the `nightly` release/tag (e.g., `>/dev/null 2>&1`), which can hide real failures (bad token, rate limiting, network issues) and lead to non-deterministic CI behavior.

## Issue Context
Compliance requires CI scripts to avoid swallowing errors that mask failures.

## Fix Focus Areas
- .github/workflows/release.yml[78-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Nightly deleted too early 🐞 Bug ☼ Reliability
Description
The nightly release and tag are now deleted in github-release-draft, which runs before/parallel to
the publish matrix, so a publish failure can still leave nightly deleted even though the release
never completed.
Code

.github/workflows/release.yml[R74-83]

+      - name: Delete nightly release and tag
+        env:
+          GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
+        run: |
+          if gh release view nightly >/dev/null 2>&1; then
+            gh release delete nightly --yes
+          fi
+          if git ls-remote --tags origin refs/tags/nightly | grep -q nightly; then
+            gh api -X DELETE /repos/${{ github.repository }}/git/refs/tags/nightly
+          fi
Evidence
github-release-draft deletes the nightly release/tag but does not depend on the publish job, so this
cleanup can execute even if publish later fails. The diff shows this cleanup was moved out of the
post-publish GitHub release job into the pre-publish draft job.

.github/workflows/release.yml[62-83]
.github/workflows/release.yml[95-113]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Nightly release/tag deletion currently happens before publish completes. If publish fails, nightly may remain deleted even though the release did not successfully publish.

### Issue Context
The goal of the PR is to create the GitHub release draft early; nightly cleanup does not need to happen early and is safer after publish success.

### Fix Focus Areas
- .github/workflows/release.yml[74-83]
- .github/workflows/release.yml[115-147]

### Suggested fix approach
- Move the "Delete nightly release and tag" step from `github-release-draft` into `github-release-publish` (or another post-publish job).
- Gate it with publish success (e.g., keep `github-release-publish` running only when `needs.publish.result == 'success'`) so nightly is only deleted when the release is actually going out.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

@selenium-ci selenium-ci added the B-build Includes scripting, bazel and CI integrations label May 7, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adjusts the Selenium release GitHub Actions workflow so a GitHub Release draft is created ahead of publishing, making recovery easier if publishing fails after tag/release creation.

Changes:

  • Adds a github-release-draft job to create (or update) a draft release before publish runs.
  • Replaces the prior github-release job with github-release-publish, which uploads artifacts and then publishes the draft (sets --draft=false).
  • Updates downstream job dependencies to use github-release-publish.

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/release.yml
Copilot AI review requested due to automatic review settings May 8, 2026 07:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/release.yml
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Copilot AI review requested due to automatic review settings May 8, 2026 12:17
@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented May 8, 2026

Persistent review updated to latest commit 4724ab8

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment on lines +155 to +162
github-release-publish:
name: GitHub Release Publish
needs: [parse-tag, publish, github-release-draft]
if: >-
needs.parse-tag.outputs.language == 'all' ||
always() && !failure() && !cancelled() &&
(needs.parse-tag.outputs.language == 'all' ||
needs.parse-tag.outputs.language == 'java' ||
needs.parse-tag.outputs.language == 'dotnet'
needs.parse-tag.outputs.language == 'dotnet')
Comment on lines 200 to +203
run: |
BASE_VERSION="${VERSION%.*}.0"
gh release upload "selenium-$BASE_VERSION" build/dist/*.* --clobber
gh release edit "selenium-$BASE_VERSION" --draft=false
Comment on lines +70 to +78
- name: Create language-specific tag
env:
GH_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}
TAG: ${{ needs.parse-tag.outputs.tag }}
SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
run: |
gh api -X POST /repos/${{ github.repository }}/git/refs \
-f ref="refs/tags/${TAG}" \
-f sha="${SHA}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Non-idempotent tag creation 🐞 Bug ☼ Reliability

create-language-tag unconditionally POSTs a new refs/tags/${TAG} without checking if it already
exists, so workflow_dispatch runs against an existing tag (or reruns after partial progress) can
fail before docs/other steps that require the tag.
Agent Prompt
### Issue description
`create-language-tag` always attempts to create `refs/tags/${TAG}` via `gh api -X POST .../git/refs`. This is not idempotent: if the tag already exists (manual pre-tagging, workflow_dispatch reruns, or reruns after partial success), the step fails and blocks downstream jobs.

### Issue Context
- Patch releases (`language != 'all'`) require a language-suffixed tag.
- Downstream docs uses this tag as the checkout ref.

### Fix Focus Areas
- .github/workflows/release.yml[62-78]

### Suggested fix approach
- Add a pre-check for the tag ref:
  - `gh api /repos/${{ github.repository }}/git/ref/tags/${TAG}`
- If it exists:
  - If it already points to `${SHA}`, treat as success and exit 0.
  - Otherwise fail with a clear error (or optionally delete/recreate if that matches your release policy).
- If it does not exist, create it (current POST).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +207 to +213
needs: [parse-tag, publish, publish-python, github-release-publish, create-language-tag]
if: >-
always() && !cancelled() &&
needs.publish.result == 'success' &&
(needs.publish-python.result == 'success' || needs.publish-python.result == 'skipped') &&
needs.github-release.result != 'failure'
needs.github-release-publish.result != 'failure' &&
needs.create-language-tag.result != 'failure'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. Draft failure bypasses docs gate 🐞 Bug ≡ Correctness

For full releases, if github-release-draft fails, github-release-publish is skipped due to the
!failure() guard; docs treats "skipped" as acceptable (!= 'failure') and can proceed even
though the release tag may not exist, causing checkout failures in the docs workflow.
Agent Prompt
### Issue description
On full releases (`language == 'all'`), a failure in `github-release-draft` can still allow `docs` to run because `github-release-publish` becomes "skipped" (due to `!failure()`), and `docs` only checks `needs.github-release-publish.result != 'failure'`. This can lead to docs trying to checkout a tag that was never created.

### Issue Context
- Tag creation for full releases happens in `github-release-draft`.
- Docs generation checks out `inputs.tag`.

### Fix Focus Areas
- .github/workflows/release.yml[155-163]
- .github/workflows/release.yml[205-214]

### Suggested fix approach
Option A (recommended): explicitly require draft success for full releases
- Add `github-release-draft` to `docs.needs`.
- Update the `docs.if` condition to include something like:
  - `(needs.parse-tag.outputs.language != 'all' || needs.github-release-draft.result == 'success')`

Option B: make `github-release-publish` fail (not skip) when draft fails
- Adjust `github-release-publish.if` to avoid converting a draft failure into a skipped job (e.g., remove `!failure()` and instead explicitly gate on `needs.publish.result == 'success'`), so downstream checks can reliably block on failure.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B-build Includes scripting, bazel and CI integrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants