Skip to content

Conversation

@titusfortner
Copy link
Member

@titusfortner titusfortner commented Jan 17, 2026

User description

This should have all the steps needed so that release can be done entirely by CI

Flow:

  • Kick off pre-release.yml workflow to generate PR
  • Verify tests passing / updates correct
  • Merge PR
  • Accept Notification for Release Approval
  • Go to Maven to do the manual review/publish
  • Celebrate 🥳

💥 What does this PR do?

Release Workflow can be kicked off manually, but goal is for it to run after pre-release PR has been merged
The pre-release PR should just be about ensuring all the tests are passing, (the changelog generation code has been dramatically simplified and should be fine as-is). Once passing, the merge commit for the PR is what gets tagged as release

release.yml workflow:

  • Set up Git & Bazel
  • Build Java & .NET packages for upload (previously done in stage-release.yml)
  • Delete nightly tag (previously done in stage-release.yml)
  • Draft GitHub Release (previously done in stage-release.yml)
  • Authorization Job when previous steps complete a GitHub notification will be sent to all TLC members, and workflow is paused until approval
  • Publish Selenium (previously manual) - releases/publishes each binding in parallel
  • Update Documentation (previously done in stage-release.yml); makes more sense to do it after publishing
  • Publish GitHub release (previously manual) - done in parallel with documentation updates
  • Bump versions to nightly (previously manual) - done after documentation updated

🔧 Implementation Notes

  • We could do this with OIDC / Trusted Publishers on GitHub, but I wanted something that would work the same on GitHub as local for right now at least
  • I've removed all requirements for having java/python/dotnet installed to do the release, and it technically should support cross-platform even though it's running on linux.

💡 Additional Considerations

This is supposed to work for patch releases, but I haven't verified it. I want to tie in pre-release for patch releases as well so it is the same flow regardless.


PR Type

Enhancement, Tests


Description

  • Implement complete release workflow automation via GitHub Actions

  • Add GPG signing support for Java releases and credential validation

  • Replace manual release staging with unified release.yml workflow

  • Update documentation generation to commit directly to gh-pages branch

  • Add credential verification rake tasks for all language bindings


Diagram Walkthrough

flowchart LR
  A["Pre-release PR merged"] --> B["Build & Stage Packages"]
  C["Manual workflow dispatch"] --> B
  B --> D["Draft GitHub Release"]
  D --> E["Authorization Gate"]
  E --> F["Publish All Languages"]
  F --> G["Update Documentation"]
  G --> H["Publish GitHub Release"]
  H --> I["Reset to Nightly Versions"]
  J["Credential Validation"] -.-> F
Loading

File Walkthrough

Relevant files
Configuration changes
bazel.yml
Add GPG signing and update authentication credentials       

.github/workflows/bazel.yml

  • Add gpg-sign input parameter for GPG key import during Java releases
  • Replace NODE_AUTH_TOKEN with GITHUB_TOKEN for npm authentication
  • Add GEM_HOST_API_KEY and NUGET_API_KEY environment variables
  • Implement GPG key import step using crazy-max/ghaction-import-gpg
    action
+14/-1   
nightly.yml
Update npm authentication token source                                     

.github/workflows/nightly.yml

  • Update JavaScript nightly release to use GITHUB_TOKEN instead of
    NODE_AUTH_TOKEN
+1/-1     
Enhancement
release.yml
Create unified automated release workflow                               

.github/workflows/release.yml

  • Create new comprehensive release workflow with multiple stages
  • Implement stage job to build packages, delete nightly tag, and draft
    GitHub release
  • Add authorization gate requiring production environment approval
  • Implement parallel publishing for all language bindings with GPG
    signing for Java
  • Add documentation update job that runs after publishing
  • Implement GitHub release publishing and version reset to nightly
  • Support both PR-triggered and manual workflow dispatch modes
+167/-0 
update-documentation.yml
Simplify docs workflow to direct gh-pages commits               

.github/workflows/update-documentation.yml

  • Refactor to accept tag parameter instead of version for better
    flexibility
  • Add tag parsing logic to extract version and language from release tag
  • Replace PR creation workflow with direct gh-pages worktree commit
    approach
  • Simplify documentation generation by removing force flag and
    auto-merge logic
  • Update checkout to use tag reference instead of SHA
+48/-43 
Rakefile
Add credential validation for release tasks                           

Rakefile

  • Add RELEASE_CREDENTIALS hash defining credential requirements for each
    language
  • Implement credential_valid? function to check environment variables,
    files, and commands
  • Implement check_credentials function to validate required credentials
    before release
  • Add credential checks to java, python, ruby, node, and dotnet release
    tasks
  • Add new all:check_credentials task for validating all language
    credentials
+49/-0   
Miscellaneous
stage-release.yml
Remove deprecated stage-release workflow                                 

.github/workflows/stage-release.yml

  • Remove entire file as functionality is consolidated into new
    release.yml
+0/-103 
update-docs-after-action.yml
Remove deprecated docs update trigger workflow                     

.github/workflows/update-docs-after-action.yml

  • Remove entire file as documentation updates are now handled within
    release.yml
+0/-34   

@selenium-ci selenium-ci added the B-build Includes scripting, bazel and CI integrations label Jan 17, 2026
@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 17, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Release trigger abuse

Description: The new release workflow auto-triggers on pull_request close/merge for branches matching
release-preparation- and then runs repo-controlled code (./go ...) while inheriting
powerful secrets into the publish phase (secrets: inherit), creating a realistic risk that
a malicious change merged into such a PR (or a compromised bot branch) could exfiltrate
release credentials or publish compromised artifacts unless the trigger is additionally
restricted (e.g., validate PR author/actor, require manual dispatch only, or enforce
approval before any secret-bearing steps).
release.yml [3-109]

Referred Code
on:
  pull_request:
    types: [closed]
  workflow_dispatch:
    inputs:
      tag:
        description: 'Release tag (e.g., selenium-4.28.0)'
        required: true

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

permissions:
  contents: read

jobs:
  stage:
    name: Build and Stage Packages
    runs-on: ubuntu-latest
    permissions:
      contents: write


 ... (clipped 86 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Credential check bypass: credential_valid? treats credentials as valid if any single check passes (env/file/cmd),
allowing releases to proceed even when required environment variables or files are
missing.

Referred Code
RELEASE_CREDENTIALS = {
  java: {
    env: [%w[MAVEN_USER SEL_M2_USER], %w[MAVEN_PASSWORD SEL_M2_PASS]],
    file: -> { File.exist?("#{Dir.home}/.m2/settings.xml") && File.read("#{Dir.home}/.m2/settings.xml").include?('<id>central</id>') }
  },
  java_gpg: {cmd: 'gpg'},
  python: {
    env: [%w[TWINE_USERNAME], %w[TWINE_PASSWORD]],
    file: -> { File.exist?("#{Dir.home}/.pypirc") }
  },
  ruby: {
    env: [%w[GEM_HOST_API_KEY]],
    file: -> { File.exist?("#{Dir.home}/.gem/credentials") }
  },
  node: {
    env: [%w[NODE_AUTH_TOKEN]],
    file: -> { File.exist?("#{Dir.home}/.npmrc") && File.read("#{Dir.home}/.npmrc").include?('//registry.npmjs.org/:_authToken') }
  },
  dotnet: {env: [%w[NUGET_API_KEY]]},
  dotnet_nightly: {env: [%w[GITHUB_TOKEN]]}
}.freeze


 ... (clipped 12 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unvalidated tag input: The user-supplied inputs.tag is used to create/refer to git tags and release metadata
without validating it against an expected safe pattern, enabling unintended refs/tags to
be used.

Referred Code
    inputs:
      tag:
        description: 'Release tag (e.g., selenium-4.28.0)'
        required: true

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

permissions:
  contents: read

jobs:
  stage:
    name: Build and Stage Packages
    runs-on: ubuntu-latest
    permissions:
      contents: write
    if: >
      github.event.repository.fork == false &&
      ((startsWith(github.event.pull_request.head.ref, 'release-preparation-') &&
       github.event.pull_request.merged == true) ||


 ... (clipped 23 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit context: The workflow performs critical actions (tag deletion, release drafting/publishing, and
publishing artifacts) without explicit structured audit logging that captures actor,
action, and outcome beyond default GitHub Actions logs.

Referred Code
stage:
  name: Build and Stage Packages
  runs-on: ubuntu-latest
  permissions:
    contents: write
  if: >
    github.event.repository.fork == false &&
    ((startsWith(github.event.pull_request.head.ref, 'release-preparation-') &&
     github.event.pull_request.merged == true) ||
    (github.event_name == 'workflow_dispatch' && github.event.inputs.tag != ''))
  outputs:
    tag: ${{ steps.tag.outputs.tag }}
    version: ${{ steps.tag.outputs.version }}
  steps:
    - name: Checkout repo
      uses: actions/checkout@v4
    - name: Extract tag and version
      id: tag
      env:
        EVENT_NAME: ${{ github.event_name }}
        INPUT_TAG: ${{ inputs.tag }}


 ... (clipped 127 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Token echoed to logs: The step appends an auth token to ~/.npmrc via echo using ${GITHUB_TOKEN}, which may be
printed in command logs depending on masking behavior and thus requires verification that
the token is always redacted.

Referred Code
echo "//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}" >> ~/.npmrc
echo "@seleniumhq:registry=https://npm.pkg.github.com" >> ~/.npmrc
echo "always-auth=true" >> ~/.npmrc

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 17, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Explicitly pass secrets to jobs

The publish job's use of secrets: inherit is insecure. It should be modified to
explicitly pass only the necessary secrets to each matrix job, adhering to the
principle of least privilege.

Examples:

.github/workflows/release.yml [96-108]
  publish:
    name: Publish ${{ matrix.language }}
    needs: authorize
    strategy:
      fail-fast: false
      matrix:
        language: [java, py, rb, dotnet, node]
    uses: ./.github/workflows/bazel.yml
    with:
      name: Publish ${{ matrix.language }}

 ... (clipped 3 lines)

Solution Walkthrough:

Before:

# in .github/workflows/release.yml
jobs:
  publish:
    name: Publish ${{ matrix.language }}
    needs: authorize
    strategy:
      matrix:
        language: [java, py, rb, dotnet, node]
    uses: ./.github/workflows/bazel.yml
    with:
      name: Publish ${{ matrix.language }}
      gpg-sign: ${{ matrix.language == 'java' }}
      run: ./go ${{ matrix.language }}:release
    secrets: inherit

After:

# in .github/workflows/release.yml
jobs:
  publish:
    name: Publish ${{ matrix.language }}
    needs: authorize
    strategy:
      matrix:
        language: [java, py, rb, dotnet, node]
    uses: ./.github/workflows/bazel.yml
    with:
      name: Publish ${{ matrix.language }}
      gpg-sign: ${{ matrix.language == 'java' }}
      run: ./go ${{ matrix.language }}:release
    secrets:
      GPG_PRIVATE_KEY: ${{ matrix.language == 'java' && secrets.GPG_PRIVATE_KEY || '' }}
      GPG_PASSPHRASE: ${{ matrix.language == 'java' && secrets.GPG_PASSPHRASE || '' }}
      SEL_M2_USER: ${{ matrix.language == 'java' && secrets.SEL_M2_USER || '' }}
      # ... etc for all other secrets and languages
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a significant security risk in the new release workflow by highlighting the use of secrets: inherit, which violates the principle of least privilege for sensitive release credentials.

High
General
Ensure tag is fetched

Add fetch-depth: 0 to the checkout step to ensure the specified git tag can be
correctly fetched and checked out.

.github/workflows/update-documentation.yml [68-71]

 - name: Checkout repository
   uses: actions/checkout@v4
   with:
+    fetch-depth: 0
     ref: ${{ inputs.tag }}
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies that checking out a tag requires a full fetch (fetch-depth: 0), fixing a critical issue that would otherwise cause the workflow to fail.

Medium
Learned
best practice
Validate workflow inputs before use
Suggestion Impact:The workflow was updated to guard against an empty INPUT_LANG before comparing it to "all" (adding a non-empty check). This aligns with the suggestion’s input validation/availability-guard intent for language, but the broader validation (tag trimming, version extraction validation, allowed-language whitelist, set -euo pipefail) was not implemented. The later git pull/push exit change is unrelated.

code diff:

@@ -61,7 +61,7 @@
             *) LANG="all" ;;
           esac
 
-          if [ "$INPUT_LANG" != "all" ]; then
+          if [ -n "$INPUT_LANG" ] && [ "$INPUT_LANG" != "all" ]; then
             LANG="$INPUT_LANG"
           fi
           echo "language=$LANG" >> "$GITHUB_OUTPUT"

Validate/trim inputs.tag and ensure a version was extracted (and language is one
of the allowed values) before writing outputs, failing fast if invalid to avoid
checking out an unexpected ref.

.github/workflows/update-documentation.yml [46-67]

 - name: Parse tag
   id: parse
   env:
     TAG: ${{ inputs.tag }}
     INPUT_LANG: ${{ inputs.language }}
   run: |
-    echo "version=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" >> "$GITHUB_OUTPUT"
+    set -euo pipefail
 
-    # Check for language suffix and map to internal names
+    TAG="$(echo "${TAG:-}" | xargs)"
+    if [ -z "$TAG" ]; then
+      echo "inputs.tag is required" >&2
+      exit 1
+    fi
+
+    VERSION="$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || true)"
+    if [ -z "$VERSION" ]; then
+      echo "Invalid tag (no version found): $TAG" >&2
+      exit 1
+    fi
+    echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+
     case "$TAG" in
       *-javascript) LANG="node" ;;
       *-python) LANG="py" ;;
       *-ruby) LANG="rb" ;;
       *-java) LANG="java" ;;
       *-dotnet) LANG="dotnet" ;;
       *) LANG="all" ;;
     esac
 
-    if [ "$INPUT_LANG" != "all" ]; then
-      LANG="$INPUT_LANG"
+    if [ -n "${INPUT_LANG:-}" ] && [ "$INPUT_LANG" != "all" ]; then
+      case "$INPUT_LANG" in
+        java|rb|py|dotnet|node|all) LANG="$INPUT_LANG" ;;
+        *) echo "Invalid language: $INPUT_LANG" >&2; exit 1 ;;
+      esac
     fi
+
     echo "language=$LANG" >> "$GITHUB_OUTPUT"

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Add explicit validation and null/availability guards at GitHub Actions integration boundaries (workflow inputs, refs) before use.

Low
Security
Prevent potential command injection vulnerability
Suggestion Impact:The patch replaced the interpolated string-based system calls with the multi-argument form and redirected output via Ruby options, addressing the command-injection concern. It also adjusted the logic to only run the check when cred[:cmd] is present (changing behavior when cred[:cmd] is nil).

code diff:

@@ -411,7 +411,7 @@
 def credential_valid?(cred)
   has_env = cred[:env]&.all? { |vars| vars.any? { |v| ENV.fetch(v, nil) } }
   has_file = cred[:file]&.call
-  has_cmd = cred[:cmd].nil? || system("which #{cred[:cmd]} > /dev/null 2>&1") || system("where #{cred[:cmd]} > nul 2>&1")
+  has_cmd = cred[:cmd] && (system('which', cred[:cmd], out: File::NULL, err: File::NULL) || system('where', cred[:cmd], out: File::NULL, err: File::NULL))
   has_env || has_file || has_cmd

Prevent a potential command injection vulnerability in credential_valid? by
using the multi-argument form of system to check for command existence, which
avoids shell interpolation.

Rakefile [411-416]

 def credential_valid?(cred)
   has_env = cred[:env]&.all? { |vars| vars.any? { |v| ENV.fetch(v, nil) } }
   has_file = cred[:file]&.call
-  has_cmd = cred[:cmd].nil? || system("which #{cred[:cmd]} > /dev/null 2>&1") || system("where #{cred[:cmd]} > nul 2>&1")
+  has_cmd = cred[:cmd].nil? || system('which', cred[:cmd], out: File::NULL, err: File::NULL) || system('where', cred[:cmd], out: File::NULL, err: File::NULL)
   has_env || has_file || has_cmd
 end

[Suggestion processed]

Suggestion importance[1-10]: 3

__

Why: The suggestion correctly identifies a potential command injection vulnerability and provides a secure fix, but the risk is low as the input comes from a hardcoded constant within the same file.

Low
Possible issue
Prevent race conditions during documentation updates
Suggestion Impact:The commit did not switch to `git pull --ff-only`, but it did modify the existing retry loop to explicitly exit successfully on a successful pull/push and to exit with failure after retries are exhausted, making race-condition failures surface as a failed job rather than silently continuing.

code diff:

             for _ in 1 2; do
-              git pull --rebase origin gh-pages && git push origin gh-pages && break
+              git pull --rebase origin gh-pages && git push origin gh-pages && exit 0
               sleep 2
             done
+            exit 1
           fi

To prevent race conditions when updating documentation, replace the git pull
--rebase retry loop with a git pull --ff-only command, which will fail the job
on conflicts needing manual resolution.

.github/workflows/update-documentation.yml [95-108]

 - name: Commit documentation to gh-pages
   run: |
     cp -r docs/api/* ../gh-pages/docs/api/
     cd ../gh-pages
     git add docs/api/
     if git diff --staged --quiet; then
       echo "No documentation changes to commit"
     else
       git commit -m "Update ${{ steps.parse.outputs.language }} documentation for Selenium ${{ steps.parse.outputs.version }}"
-      for _ in 1 2; do
-        git pull --rebase origin gh-pages && git push origin gh-pages && break
-        sleep 2
-      done
+      git pull origin gh-pages --ff-only
+      git push origin gh-pages
     fi

[Suggestion processed]

Suggestion importance[1-10]: 2

__

Why: The suggestion correctly identifies a potential race condition, but the proposed solution using git pull --ff-only is less resilient than the existing retry loop and would likely cause more job failures in a concurrent environment.

Low
  • Update

Copy link
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

This PR implements a fully automated release workflow for Selenium that can be triggered from CI, eliminating manual steps previously required for releases. The workflow handles building packages, authorization via GitHub environments, parallel publishing of all language bindings, documentation updates, and version bumping back to nightly.

Changes:

  • Added comprehensive credential validation in Rakefile to check release prerequisites
  • Created new release.yml workflow that orchestrates the entire release process with authorization gates
  • Streamlined documentation updates to commit directly to gh-pages instead of creating PRs
  • Removed obsolete workflows (stage-release.yml, update-docs-after-action.yml)

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Rakefile Added credential validation functions and integrated checks into each language's release task
.github/workflows/release.yml New comprehensive release workflow with staging, authorization, parallel publishing, and post-release tasks
.github/workflows/update-documentation.yml Refactored to accept tag input, parse language from tag suffix, and commit directly to gh-pages
.github/workflows/update-docs-after-action.yml Deleted - no longer needed with new direct commit approach
.github/workflows/stage-release.yml Deleted - functionality moved into release.yml
.github/workflows/nightly.yml Fixed token reference to use GITHUB_TOKEN instead of NODE_AUTH_TOKEN
.github/workflows/bazel.yml Added gpg-sign input parameter and new secret environment variables for releases

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@titusfortner titusfortner merged commit ad238dd into trunk Jan 17, 2026
20 checks passed
@titusfortner titusfortner deleted the release_workflow branch January 17, 2026 21:28
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 Review effort 4/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants