Skip to content

Conversation

@titusfortner
Copy link
Member

@titusfortner titusfortner commented Jan 23, 2026

User description

This should work the same as a full release now.
Whether Minor release or Patch release, kick of the pre-release workflow and specify the tagname
and the script should do the rest. Once the PR is up, merge it, release workflow will start,
and Bob should be your uncle.

🔗 Related Issues

In Draft until we merge: #16979

💥 What does this PR do?

Enable patch releases for individual language bindings (e.g., selenium-4.28.1-ruby) without requiring a full release of all components.

Tag format and validation:

  • Accept tags like selenium-4.28.1-ruby, selenium-4.28.1-python, etc.
  • Patch releases (X.Y.Z where Z>0) must have a language suffix
  • Full releases (X.Y.0) cannot have a language suffix

Pre-release workflow:

  • Parse tag to extract version and language
  • Skip Selenium Manager, browser pins, CDP updates for patch releases
  • Run only the relevant language's version/changelog tasks
  • Show "N/A" in PR body table for skipped components

Release workflow:

  • Filter publish and docs matrix to selected language only
  • Skip GitHub release creation for patch releases
  • Skip nightly and mirror jobs for patch releases

Valid language suffixes: ruby, python, java, javascript, dotnet

🔧 Implementation Notes

  • Reuses existing per-language rake tasks from rakefile-split (rb:version, rb:changelog, etc.)
  • Version tasks now invoke their own update tasks (java, node) to ensure dependencies are current
  • Can now use full language names in workflows (ruby instead of rb) since namespace aliases support them
  • Removed release_update wrapper task in favor of calling update_multitool directly

💡 Additional Considerations

Who wants to try it?

🔄 Types of changes

  • New feature (non-breaking* change which adds functionality)

*hopefully


PR Type

Enhancement


Description

  • Enable per-language patch releases (e.g., selenium-4.28.1-ruby) without full release

  • Parse and validate release tags with language suffix support

  • Skip Rust/SM, browser pins, CDP updates for patch releases

  • Filter publish/docs matrix to selected language only

  • Make version tasks invoke their own update tasks automatically


Diagram Walkthrough

flowchart LR
  A["Release Tag Input<br/>selenium-4.28.1-ruby"] --> B["Parse Tag Job"]
  B --> C{Language<br/>Suffix?}
  C -->|Patch Release| D["Extract Language<br/>ruby/python/java/etc"]
  C -->|Full Release| E["Language = all"]
  D --> F["Skip Rust/SM/Browser/CDP"]
  E --> G["Run All Updates"]
  F --> H["Per-Language Version<br/>& Changelog Tasks"]
  G --> I["All Component Updates"]
  H --> J["Filtered Publish Matrix"]
  I --> K["Full Publish Matrix"]
Loading

File Walkthrough

Relevant files
Enhancement
java.rake
Invoke update task after version bump                                       

rake_tasks/java.rake

  • Added automatic invocation of java:update task after version bump
  • Ensures dependencies are updated when version changes
+2/-0     
node.rake
Invoke update task after version bump                                       

rake_tasks/node.rake

  • Added automatic invocation of node:update task after version bump
  • Ensures dependencies are updated when version changes
+2/-0     
pre-release.yml
Add tag parsing and per-language release support                 

.github/workflows/pre-release.yml

  • Replaced version input with tag input supporting language suffixes
  • Added parse-tag job to extract version, language, and validate tag
    format
  • Validate patch releases (Z>0) must have language suffix, full releases
    (Z=0) cannot
  • Skip Rust/SM, browser, devtools, multitool, authors jobs for patch
    releases
  • Use per-language version/changelog tasks instead of all:version and
    all:changelogs
  • Updated PR body table to show "N/A" for skipped components in patch
    releases
  • Renamed "dependencies" to "multitool" for clarity
  • Changed release_update to update_multitool direct call
+118/-46
release.yml
Filter release jobs by language and skip for patch releases

.github/workflows/release.yml

  • Updated prepare job to parse tag and extract language suffix
  • Added language validation and patch release requirements
  • Filter publish matrix to run only for selected language
  • Skip github-release job for patch releases (language != 'all')
  • Skip nightly and mirror jobs for patch releases
  • Use per-language verify task instead of all:verify
  • Use per-language version nightly task instead of all:version nightly
  • Changed language matrix values from abbreviations (py, rb, node) to
    full names (python, ruby, javascript)
+56/-17 
update-documentation.yml
Use full language names in documentation workflow               

.github/workflows/update-documentation.yml

  • Updated language options to use full names (ruby, python, javascript)
    instead of abbreviations
  • Updated tag parsing to map language suffixes to full names
+7/-7     
Cleanup
Rakefile
Remove release_update wrapper task                                             

Rakefile

  • Removed release_update wrapper task that only called update_multitool
  • Workflows now call update_multitool directly
+0/-5     

@titusfortner titusfortner requested a review from Copilot January 23, 2026 04:39
@selenium-ci selenium-ci added C-rb Ruby Bindings B-build Includes scripting, bazel and CI integrations labels Jan 23, 2026
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 enables per-language patch releases for individual Selenium language bindings (e.g., selenium-4.28.1-ruby), allowing targeted updates without requiring a full release of all components.

Changes:

  • Implements tag format validation: patch releases (X.Y.Z where Z>0) must have a language suffix, while full releases (X.Y.0) cannot
  • Refactors Rakefile by splitting into modular per-language task files (from PR #16979) to support language-specific release workflows
  • Updates pre-release and release workflows to parse language-specific tags and conditionally skip Selenium Manager, browser pins, CDP updates, and GitHub release creation for patch releases

Reviewed changes

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

Show a summary per file
File Description
.github/workflows/pre-release.yml Adds tag parsing logic with language detection and validation; conditionally skips global update jobs for patch releases
.github/workflows/release.yml Filters publish and docs matrix jobs by detected language; skips GitHub release, nightly, and mirror jobs for patch releases
.github/workflows/update-documentation.yml Updates language parameter mappings from abbreviations (rb, py, node) to full names (ruby, python, javascript)
Rakefile Loads language-specific rake files into namespaced tasks with full-name aliases; adds prep_release, update_cdp, and lint tasks
rake_tasks/*.rake New modular task files for each language (java, ruby, python, node, dotnet, rust, grid, appium)
rake_tasks/common.rb Shared SeleniumRake module with utilities for version bumping, changelog generation, and package verification
scripts/update_cdp.py Updates file path reference from Rakefile to rake_tasks/java.rake
rb/.rubocop.yml Adds rake_tasks/ directory to Rubocop exclusions
rb/BUILD.bazel Adds rake_tasks/ to linting arguments
BUILD.bazel Updates rakefile filegroup to include all rake task files

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 24, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
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: Comprehensive Audit Trails

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

Status: Passed

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

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: Secure Logging Practices

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

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:
Missing tag validation: The new tag parsing step does not validate the overall tag format and can produce
empty/non-numeric VERSION/PATCH values that lead to unclear shell errors (e.g., failed -gt
comparisons) instead of actionable messages.

Referred Code
run: |
  if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
    TAG="$INPUT_TAG"
  else
    # Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
    TAG=$(echo "$PR_HEAD_REF" | sed 's/^release-preparation-//')
  fi

  # Extract version
  VERSION=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
  PATCH=$(echo "$VERSION" | cut -d. -f3)

  # Extract language suffix
  if [[ "$TAG" =~ -([a-z]+)$ ]]; then
    LANG_SUFFIX="${BASH_REMATCH[1]}"
  else
    LANG_SUFFIX=""
  fi

  # Patch releases must have a language suffix
  if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then


 ... (clipped 17 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:
Incomplete input validation: The workflow accepts and parses ${{ inputs.tag }} / derived branch tag without enforcing
the documented rules (e.g., full releases must not have a language suffix and tag should
match selenium-X.Y.Z(-lang)?), allowing invalid inputs to proceed into release logic.

Referred Code
run: |
  if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
    TAG="$INPUT_TAG"
  else
    # Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
    TAG=$(echo "$PR_HEAD_REF" | sed 's/^release-preparation-//')
  fi

  # Extract version
  VERSION=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
  PATCH=$(echo "$VERSION" | cut -d. -f3)

  # Extract language suffix
  if [[ "$TAG" =~ -([a-z]+)$ ]]; then
    LANG_SUFFIX="${BASH_REMATCH[1]}"
  else
    LANG_SUFFIX=""
  fi

  # Patch releases must have a language suffix
  if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then


 ... (clipped 22 lines)

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 24, 2026

PR Code Suggestions ✨

Latest suggestions up to 41e69bd

CategorySuggestion                                                                                                                                    Impact
Possible issue
Map suffixes to task namespaces

Revert the publish job's language matrix to use short names (py, rb, node) and
update the if condition to map the full language names from the tag to these
short names to prevent task failures.

.github/workflows/release.yml [62-76]

 publish:
   name: Build and Publish ${{ matrix.language }}
   needs: [prepare, get-approval]
-  if: needs.prepare.outputs.language == 'all' || needs.prepare.outputs.language == matrix.language
+  if: |
+    needs.prepare.outputs.language == 'all' ||
+    (needs.prepare.outputs.language == 'python' && matrix.language == 'py') ||
+    (needs.prepare.outputs.language == 'ruby' && matrix.language == 'rb') ||
+    (needs.prepare.outputs.language == 'javascript' && matrix.language == 'node') ||
+    needs.prepare.outputs.language == matrix.language
   strategy:
     fail-fast: false
     matrix:
-      language: [java, python, ruby, dotnet, javascript]
+      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
     artifact-name: release-packages-${{ matrix.language }}
     artifact-path: build/dist/*.*
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that the PR changes the language matrix to full names (python, ruby, javascript) while the underlying build tasks likely still use short names (py, rb, node), which would cause the publish job to fail.

High
Use internal doc task names

In the case statement, map the language suffixes from tags (e.g., *-python) to
their corresponding internal task names (e.g., py) to ensure documentation tasks
run correctly.

.github/workflows/update-documentation.yml [58-66]

-# Extract language suffix (rake namespace aliases allow full names)
+# Extract language suffix (map tag suffixes to internal task namespaces)
 case "$TAG" in
-  *-javascript) LANG="javascript" ;;
-  *-python) LANG="python" ;;
-  *-ruby) LANG="ruby" ;;
+  *-javascript) LANG="node" ;;
+  *-python) LANG="py" ;;
+  *-ruby) LANG="rb" ;;
   *-java) LANG="java" ;;
   *-dotnet) LANG="dotnet" ;;
   *) LANG="all" ;;
 esac
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that changing the language mapping to full names (python, ruby, javascript) will break the documentation update step, as the underlying Rake tasks still use short names (py, rb, node).

High
Stage changes after updates

Reorder the operations in the java:version task to invoke java:update before
staging any files with git.add, ensuring all changes are included in the commit.

rake_tasks/java.rake [382-387]

 file = 'java/version.bzl'
 text = File.read(file).gsub(old_version, new_version)
 File.open(file, 'w') { |f| f.puts text }
+
+Rake::Task['java:update'].invoke
 SeleniumRake.git.add(file)
 
-Rake::Task['java:update'].invoke
-
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that staging files should happen after all modifications are complete, and the PR's ordering could lead to an incomplete commit, which is a valid and important concern for release automation.

Medium
Learned
best practice
Make tag parsing fail-fast

Make the bash step fail-fast (set -euo pipefail) and avoid relying on external
commands whose failures can silently produce empty outputs (e.g., sed, cut), so
invalid/unexpected inputs can't slip through.

.github/workflows/parse-release-tag.yml [35-47]

 run: |
+  set -euo pipefail
+
   TAG="${{ inputs.tag }}"
   TAG="${TAG//[[:space:]]/}"
 
   # Validate tag format: selenium-X.Y.Z or selenium-X.Y.Z-lang
   if [[ ! "$TAG" =~ ^selenium-[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+)?$ ]]; then
     echo "::error::Invalid tag format: '$TAG'. Expected selenium-X.Y.Z or selenium-X.Y.Z-lang"
     exit 1
   fi
 
   # Extract version (strip 'selenium-' prefix and optional language suffix)
-  VERSION=$(echo "$TAG" | sed -E 's/^selenium-([0-9]+\.[0-9]+\.[0-9]+)(-[a-z]+)?$/\1/')
-  PATCH=$(echo "$VERSION" | cut -d. -f3)
+  VERSION="${TAG#selenium-}"
+  VERSION="${VERSION%-${VERSION##*-}}"
+  PATCH="${VERSION##*.}"
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Add explicit validation/guards at integration boundaries; make shell parsing fail-fast to avoid using unset/failed command outputs.

Low
Stage modified version files

Explicitly git add the files you modify in the task (as done in the Java tasks),
so patch generation/commit steps reliably include the version bumps even if
downstream tasks change behavior.

rake_tasks/node.rake [118-123]

 %w[javascript/selenium-webdriver/package.json javascript/selenium-webdriver/BUILD.bazel].each do |file|
   text = File.read(file).gsub(old_version, new_version)
   File.open(file, 'w') { |f| f.puts text }
+  SeleniumRake.git.add(file)
 end
 
 Rake::Task['node:update'].invoke

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Make lifecycle/side-effectful tasks robust and explicit by staging the files they modify (avoid hidden side effects in downstream steps).

Low
  • Update

Previous suggestions

✅ Suggestions up to commit 0ee8540
CategorySuggestion                                                                                                                                    Impact
High-level
Centralize duplicated release tag parsing logic
Suggestion Impact:The duplicated tag parsing/validation shell logic was removed from both pre-release.yml and release.yml and replaced with calls to a shared reusable workflow (./.github/workflows/parse-release-tag.yml). Release.yml was refactored to first extract the tag, then invoke the shared parser job, and downstream jobs now use the parsed outputs (e.g., gating reset/update version on language == 'all').

code diff:

# File: .github/workflows/pre-release.yml
@@ -105,65 +105,9 @@
 
   parse-tag:
     name: Parse Tag
-    runs-on: ubuntu-latest
-    outputs:
-      tag: ${{ steps.parse.outputs.tag }}
-      version: ${{ steps.parse.outputs.version }}
-      language: ${{ steps.parse.outputs.language }}
-    steps:
-      - name: Parse tag
-        id: parse
-        shell: bash
-        run: |
-          TAG="${{ inputs.tag }}"
-          TAG="${TAG//[[:space:]]/}"
-
-          # Validate tag format: selenium-X.Y.Z or selenium-X.Y.Z-lang
-          if [[ ! "$TAG" =~ ^selenium-[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+)?$ ]]; then
-            echo "::error::Invalid tag format: '$TAG'. Expected selenium-X.Y.Z or selenium-X.Y.Z-lang"
-            exit 1
-          fi
-
-          # Extract version (strip 'selenium-' prefix and optional language suffix)
-          VERSION=$(echo "$TAG" | sed -E 's/^selenium-([0-9]+\.[0-9]+\.[0-9]+)(-[a-z]+)?$/\1/')
-          PATCH=$(echo "$VERSION" | cut -d. -f3)
-
-          # Extract language suffix (default to 'all' if no suffix)
-          if [[ "$TAG" =~ -([a-z]+)$ ]]; then
-            LANG_SUFFIX="${BASH_REMATCH[1]}"
-          else
-            LANG_SUFFIX=""
-          fi
-
-          # Patch releases must have a language suffix
-          if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then
-            echo "::error::Patch releases must specify a language (e.g., selenium-${VERSION}-ruby)"
-            exit 1
-          fi
-
-          # Full releases (X.Y.0) must not have a language suffix
-          if [[ "$PATCH" -eq 0 && -n "$LANG_SUFFIX" ]]; then
-            echo "::error::Full releases (X.Y.0) cannot have a language suffix"
-            exit 1
-          fi
-
-          # Validate language suffix (rake namespace aliases allow full names)
-          case "$LANG_SUFFIX" in
-            ruby|python|javascript|java|dotnet)
-              LANGUAGE="$LANG_SUFFIX"
-              ;;
-            "")
-              LANGUAGE="all"
-              ;;
-            *)
-              echo "::error::Invalid language suffix: '$LANG_SUFFIX'. Expected ruby, python, javascript, java, or dotnet"
-              exit 1
-              ;;
-          esac
-
-          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
-          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
-          echo "language=$LANGUAGE" >> "$GITHUB_OUTPUT"
+    uses: ./.github/workflows/parse-release-tag.yml
+    with:
+      tag: ${{ inputs.tag }}
 
   generate-updates:
     name: Generate ${{ matrix.name }}

# File: .github/workflows/release.yml
@@ -16,8 +16,8 @@
   contents: read
 
 jobs:
-  prepare:
-    name: Prepare Release
+  extract-tag:
+    name: Extract Tag
     runs-on: ubuntu-latest
     if: >
       github.event.repository.fork == false &&
@@ -25,12 +25,10 @@
        github.event.pull_request.merged == true) ||
       (github.event_name == 'workflow_dispatch' && github.event.inputs.tag != ''))
     outputs:
-      tag: ${{ steps.parse.outputs.tag }}
-      version: ${{ steps.parse.outputs.version }}
-      language: ${{ steps.parse.outputs.language }}
+      tag: ${{ steps.extract.outputs.tag }}
     steps:
-      - name: Extract and parse tag
-        id: parse
+      - name: Extract tag from input or PR branch
+        id: extract
         env:
           EVENT_NAME: ${{ github.event_name }}
           INPUT_TAG: ${{ inputs.tag }}
@@ -42,41 +40,14 @@
             # Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
             TAG=$(echo "$PR_HEAD_REF" | sed 's/^release-preparation-//')
           fi
-
-          # Extract version
-          VERSION=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
-          PATCH=$(echo "$VERSION" | cut -d. -f3)
-
-          # Extract language suffix
-          if [[ "$TAG" =~ -([a-z]+)$ ]]; then
-            LANG_SUFFIX="${BASH_REMATCH[1]}"
-          else
-            LANG_SUFFIX=""
-          fi
-
-          # Patch releases must have a language suffix
-          if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then
-            echo "::error::Patch releases must specify a language (e.g., selenium-${VERSION}-ruby)"
-            exit 1
-          fi
-
-          # Validate language suffix (rake namespace aliases allow full names)
-          case "$LANG_SUFFIX" in
-            ruby|python|javascript|java|dotnet)
-              LANGUAGE="$LANG_SUFFIX"
-              ;;
-            "")
-              LANGUAGE="all"
-              ;;
-            *)
-              echo "::error::Invalid language suffix: '$LANG_SUFFIX'. Expected ruby, python, javascript, java, or dotnet"
-              exit 1
-              ;;
-          esac
-
           echo "tag=$TAG" >> "$GITHUB_OUTPUT"
-          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
-          echo "language=$LANGUAGE" >> "$GITHUB_OUTPUT"
+
+  prepare:
+    name: Parse Tag
+    needs: extract-tag
+    uses: ./.github/workflows/parse-release-tag.yml
+    with:
+      tag: ${{ needs.extract-tag.outputs.tag }}
 
   get-approval:
     name: Get Approval
@@ -179,6 +150,7 @@
   reset-version:
     name: Generate Nightly Versions
     needs: [prepare, docs]
+    if: needs.prepare.outputs.language == 'all'
     uses: ./.github/workflows/bazel.yml
     with:
       name: Reset Versions
@@ -188,6 +160,7 @@
   update-version:
     name: Push Nightly Versions
     needs: [prepare, reset-version, unrestrict-trunk]
+    if: needs.prepare.outputs.language == 'all'
     uses: ./.github/workflows/commit-changes.yml

The release tag parsing and validation logic is duplicated across
pre-release.yml and release.yml. This logic should be extracted into a single
reusable workflow to serve as a single source of truth, improving
maintainability.

Examples:

.github/workflows/pre-release.yml [117-166]
        run: |
          TAG="${{ inputs.tag }}"
          TAG="${TAG//[[:space:]]/}"

          # Validate tag format: selenium-X.Y.Z or selenium-X.Y.Z-lang
          if [[ ! "$TAG" =~ ^selenium-[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+)?$ ]]; then
            echo "::error::Invalid tag format: '$TAG'. Expected selenium-X.Y.Z or selenium-X.Y.Z-lang"
            exit 1
          fi


 ... (clipped 40 lines)
.github/workflows/release.yml [38-79]
        run: |
          if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
            TAG="$INPUT_TAG"
          else
            # Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
            TAG=$(echo "$PR_HEAD_REF" | sed 's/^release-preparation-//')
          fi

          # Extract version
          VERSION=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')

 ... (clipped 32 lines)

Solution Walkthrough:

Before:

# In .github/workflows/pre-release.yml
jobs:
  parse-tag:
    steps:
      - name: Parse tag
        run: |
          TAG="${{ inputs.tag }}"
          # ... complex parsing and validation logic ...
          if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then
            echo "::error::Patch releases must specify a language"
            exit 1
          fi
          # ... more validation ...

# In .github/workflows/release.yml
jobs:
  prepare:
    steps:
      - name: Extract and parse tag
        run: |
          # ... logic to get TAG from branch or input ...
          # ... complex parsing and validation logic (duplicated) ...
          if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then
            echo "::error::Patch releases must specify a language"
            exit 1
          fi
          # ... more validation (duplicated) ...

After:

# In .github/workflows/parse-release-tag.yml (new file)
on:
  workflow_call:
    inputs:
      tag_string:
        type: string
    outputs:
      tag:
      version:
      language:
jobs:
  parse:
    steps:
      - name: Parse tag
        run: |
          # ... single source of truth for parsing and validation ...
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
          echo "language=$LANGUAGE" >> "$GITHUB_OUTPUT"

# In .github/workflows/pre-release.yml and release.yml
jobs:
  parse-tag: # or 'prepare' job
    uses: ./.github/workflows/parse-release-tag.yml
    with:
      tag_string: ${{ inputs.tag }} # or ${{ github.event.pull_request.head.ref }}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies significant and complex logic duplication for tag parsing between pre-release.yml and release.yml, and proposing a reusable workflow is an excellent architectural improvement for maintainability.

Medium
Possible issue
Add missing tag validation logic
Suggestion Impact:The commit refactors tag extraction/parsing in release.yml: it removes the inline parsing/validation logic from the workflow and instead calls a reusable workflow (parse-release-tag.yml) after extracting the raw tag. This change is in the same area as the suggested validation and appears aimed at centralizing/aligning tag parsing/validation logic, though the specific "X.Y.0 must not have suffix" check is not shown in this diff.

code diff:

@@ -16,8 +16,8 @@
   contents: read
 
 jobs:
-  prepare:
-    name: Prepare Release
+  extract-tag:
+    name: Extract Tag
     runs-on: ubuntu-latest
     if: >
       github.event.repository.fork == false &&
@@ -25,12 +25,10 @@
        github.event.pull_request.merged == true) ||
       (github.event_name == 'workflow_dispatch' && github.event.inputs.tag != ''))
     outputs:
-      tag: ${{ steps.parse.outputs.tag }}
-      version: ${{ steps.parse.outputs.version }}
-      language: ${{ steps.parse.outputs.language }}
+      tag: ${{ steps.extract.outputs.tag }}
     steps:
-      - name: Extract and parse tag
-        id: parse
+      - name: Extract tag from input or PR branch
+        id: extract
         env:
           EVENT_NAME: ${{ github.event_name }}
           INPUT_TAG: ${{ inputs.tag }}
@@ -42,41 +40,14 @@
             # Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
             TAG=$(echo "$PR_HEAD_REF" | sed 's/^release-preparation-//')
           fi
-
-          # Extract version
-          VERSION=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
-          PATCH=$(echo "$VERSION" | cut -d. -f3)
-
-          # Extract language suffix
-          if [[ "$TAG" =~ -([a-z]+)$ ]]; then
-            LANG_SUFFIX="${BASH_REMATCH[1]}"
-          else
-            LANG_SUFFIX=""
-          fi
-
-          # Patch releases must have a language suffix
-          if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then
-            echo "::error::Patch releases must specify a language (e.g., selenium-${VERSION}-ruby)"
-            exit 1
-          fi
-
-          # Validate language suffix (rake namespace aliases allow full names)
-          case "$LANG_SUFFIX" in
-            ruby|python|javascript|java|dotnet)
-              LANGUAGE="$LANG_SUFFIX"
-              ;;
-            "")
-              LANGUAGE="all"
-              ;;
-            *)
-              echo "::error::Invalid language suffix: '$LANG_SUFFIX'. Expected ruby, python, javascript, java, or dotnet"
-              exit 1
-              ;;
-          esac
-
           echo "tag=$TAG" >> "$GITHUB_OUTPUT"
-          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
-          echo "language=$LANGUAGE" >> "$GITHUB_OUTPUT"
+
+  prepare:
+    name: Parse Tag
+    needs: extract-tag
+    uses: ./.github/workflows/parse-release-tag.yml
+    with:
+      tag: ${{ needs.extract-tag.outputs.tag }}
 

Add validation to the prepare job in release.yml to ensure full releases do not
have a language suffix, aligning its logic with the pre-release.yml workflow.

.github/workflows/release.yml [32-79]

 - name: Extract and parse tag
   id: parse
   env:
     EVENT_NAME: ${{ github.event_name }}
     INPUT_TAG: ${{ inputs.tag }}
     PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
   run: |
     if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
       TAG="$INPUT_TAG"
     else
       # Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
       TAG=$(echo "$PR_HEAD_REF" | sed 's/^release-preparation-//')
     fi
 
     # Extract version
     VERSION=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
     PATCH=$(echo "$VERSION" | cut -d. -f3)
 
     # Extract language suffix
     if [[ "$TAG" =~ -([a-z]+)$ ]]; then
       LANG_SUFFIX="${BASH_REMATCH[1]}"
     else
       LANG_SUFFIX=""
     fi
 
     # Patch releases must have a language suffix
     if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then
       echo "::error::Patch releases must specify a language (e.g., selenium-${VERSION}-ruby)"
       exit 1
     fi
 
+    # Full releases (X.Y.0) must not have a language suffix
+    if [[ "$PATCH" -eq 0 && -n "$LANG_SUFFIX" ]]; then
+      echo "::error::Full releases (X.Y.0) cannot have a language suffix"
+      exit 1
+    fi
+
     # Validate language suffix (rake namespace aliases allow full names)
     case "$LANG_SUFFIX" in
       ruby|python|javascript|java|dotnet)
         LANGUAGE="$LANG_SUFFIX"
         ;;
       "")
         LANGUAGE="all"
         ;;
       *)
         echo "::error::Invalid language suffix: '$LANG_SUFFIX'. Expected ruby, python, javascript, java, or dotnet"
         exit 1
         ;;
     esac
 
     echo "tag=$TAG" >> "$GITHUB_OUTPUT"
     echo "version=$VERSION" >> "$GITHUB_OUTPUT"
     echo "language=$LANGUAGE" >> "$GITHUB_OUTPUT"

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies missing validation logic in the release.yml workflow, which could lead to inconsistent behavior compared to pre-release.yml, and proposes adding it to improve robustness.

Medium
Learned
best practice
Centralize tag parsing logic
Suggestion Impact:The inline bash tag parsing/validation steps were removed and replaced with a call to a reusable workflow (./.github/workflows/parse-release-tag.yml) passing the tag input, centralizing the logic.

code diff:

@@ -105,65 +105,9 @@
 
   parse-tag:
     name: Parse Tag
-    runs-on: ubuntu-latest
-    outputs:
-      tag: ${{ steps.parse.outputs.tag }}
-      version: ${{ steps.parse.outputs.version }}
-      language: ${{ steps.parse.outputs.language }}
-    steps:
-      - name: Parse tag
-        id: parse
-        shell: bash
-        run: |
-          TAG="${{ inputs.tag }}"
-          TAG="${TAG//[[:space:]]/}"
-
-          # Validate tag format: selenium-X.Y.Z or selenium-X.Y.Z-lang
-          if [[ ! "$TAG" =~ ^selenium-[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+)?$ ]]; then
-            echo "::error::Invalid tag format: '$TAG'. Expected selenium-X.Y.Z or selenium-X.Y.Z-lang"
-            exit 1
-          fi
-
-          # Extract version (strip 'selenium-' prefix and optional language suffix)
-          VERSION=$(echo "$TAG" | sed -E 's/^selenium-([0-9]+\.[0-9]+\.[0-9]+)(-[a-z]+)?$/\1/')
-          PATCH=$(echo "$VERSION" | cut -d. -f3)
-
-          # Extract language suffix (default to 'all' if no suffix)
-          if [[ "$TAG" =~ -([a-z]+)$ ]]; then
-            LANG_SUFFIX="${BASH_REMATCH[1]}"
-          else
-            LANG_SUFFIX=""
-          fi
-
-          # Patch releases must have a language suffix
-          if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then
-            echo "::error::Patch releases must specify a language (e.g., selenium-${VERSION}-ruby)"
-            exit 1
-          fi
-
-          # Full releases (X.Y.0) must not have a language suffix
-          if [[ "$PATCH" -eq 0 && -n "$LANG_SUFFIX" ]]; then
-            echo "::error::Full releases (X.Y.0) cannot have a language suffix"
-            exit 1
-          fi
-
-          # Validate language suffix (rake namespace aliases allow full names)
-          case "$LANG_SUFFIX" in
-            ruby|python|javascript|java|dotnet)
-              LANGUAGE="$LANG_SUFFIX"
-              ;;
-            "")
-              LANGUAGE="all"
-              ;;
-            *)
-              echo "::error::Invalid language suffix: '$LANG_SUFFIX'. Expected ruby, python, javascript, java, or dotnet"
-              exit 1
-              ;;
-          esac
-
-          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
-          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
-          echo "language=$LANGUAGE" >> "$GITHUB_OUTPUT"
+    uses: ./.github/workflows/parse-release-tag.yml
+    with:
+      tag: ${{ inputs.tag }}
 

Move tag parsing/validation into a single shared script (or reusable
workflow/composite action) and call it from all workflows to prevent future
drift and inconsistent rules.

.github/workflows/pre-release.yml [114-166]

 - name: Parse tag
   id: parse
   shell: bash
   run: |
-    TAG="${{ inputs.tag }}"
-    TAG="${TAG//[[:space:]]/}"
+    ./scripts/parse-release-tag.sh "${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
 
-    # Validate tag format: selenium-X.Y.Z or selenium-X.Y.Z-lang
-    if [[ ! "$TAG" =~ ^selenium-[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+)?$ ]]; then
-      echo "::error::Invalid tag format: '$TAG'. Expected selenium-X.Y.Z or selenium-X.Y.Z-lang"
-      exit 1
-    fi
-
-    # Extract version (strip 'selenium-' prefix and optional language suffix)
-    VERSION=$(echo "$TAG" | sed -E 's/^selenium-([0-9]+\.[0-9]+\.[0-9]+)(-[a-z]+)?$/\1/')
-    PATCH=$(echo "$VERSION" | cut -d. -f3)
-
-    # Extract language suffix (default to 'all' if no suffix)
-    if [[ "$TAG" =~ -([a-z]+)$ ]]; then
-      LANG_SUFFIX="${BASH_REMATCH[1]}"
-    else
-      LANG_SUFFIX=""
-    fi
-
-    # Patch releases must have a language suffix
-    if [[ "$PATCH" -gt 0 && -z "$LANG_SUFFIX" ]]; then
-      echo "::error::Patch releases must specify a language (e.g., selenium-${VERSION}-ruby)"
-      exit 1
-    fi
-
-    # Full releases (X.Y.0) must not have a language suffix
-    if [[ "$PATCH" -eq 0 && -n "$LANG_SUFFIX" ]]; then
-      echo "::error::Full releases (X.Y.0) cannot have a language suffix"
-      exit 1
-    fi
-
-    # Validate language suffix (rake namespace aliases allow full names)
-    case "$LANG_SUFFIX" in
-      ruby|python|javascript|java|dotnet)
-        LANGUAGE="$LANG_SUFFIX"
-        ;;
-      "")
-        LANGUAGE="all"
-        ;;
-      *)
-        echo "::error::Invalid language suffix: '$LANG_SUFFIX'. Expected ruby, python, javascript, java, or dotnet"
-        exit 1
-        ;;
-    esac
-
-    echo "tag=$TAG" >> "$GITHUB_OUTPUT"
-    echo "version=$VERSION" >> "$GITHUB_OUTPUT"
-    echo "language=$LANGUAGE" >> "$GITHUB_OUTPUT"
-

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Reduce duplication by centralizing shared behavior (single-source parsing/validation logic) instead of copy/pasting similar logic across workflows.

Low

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

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

Comments suppressed due to low confidence (1)

.github/workflows/release.yml:196

  • The update-version job should only run for full releases, not for patch releases. This job depends on reset-version and should follow the same conditional logic. Add a conditional check to skip this job for patch releases:

Change line 190 to:

needs: [prepare, reset-version, unrestrict-trunk]
if: needs.prepare.outputs.language == 'all'
  update-version:
    name: Push Nightly Versions
    needs: [prepare, reset-version, unrestrict-trunk]
    uses: ./.github/workflows/commit-changes.yml
    with:
      artifact-name: version-reset
      commit-message: "[build] Reset versions to nightly after ${{ needs.prepare.outputs.tag }} release"
    secrets:
      SELENIUM_CI_TOKEN: ${{ secrets.SELENIUM_CI_TOKEN }}

titusfortner and others added 7 commits January 24, 2026 00:33
- pre-release.yml: Accept tag input (e.g., selenium-4.28.1-ruby)
- Parse tag to extract version and language (ruby->rb, python->py, etc.)
- Validate: patch releases (X.Y.Z where Z>0) must have language suffix
- Validate: full releases (X.Y.0) cannot have language suffix
- Skip Rust/SM, browser, devtools, dependency updates for patch releases
- Use per-language version/changelog tasks (e.g., ./go rb:version)

- release.yml: Parse language from PR branch or tag input
- Filter publish/docs matrix to only run for selected language
- Use per-language verify and version reset tasks
- Skip nightly/mirror jobs for patch releases

- Rakefile: Rename all:changelogs to all:changelog for consistency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Skip github-release job for patch releases (language != 'all')
- Update docs job to run when github-release is skipped
- Show "N/A" in PR body for components not run during patch releases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove release_update wrapper task (just called update_multitool)
- Update workflow to call update_multitool directly
- Rename "dependencies" step/label to "multitool" for clarity
- Use update_cdp instead of all:update_cdp (task is in root namespace)
- Add parse-release-tag.yml for shared tag parsing/validation
- Update pre-release.yml to use reusable workflow
- Update release.yml to use reusable workflow
- Skip version reset jobs for patch releases
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

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

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 C-rb Ruby Bindings Review effort 3/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants