Skip to content

[build] Support per-language patch releases#16987

Merged
titusfortner merged 9 commits intotrunkfrom
patch_release
Jan 25, 2026
Merged

[build] Support per-language patch releases#16987
titusfortner merged 9 commits intotrunkfrom
patch_release

Conversation

@titusfortner
Copy link
Member

@titusfortner titusfortner commented Jan 23, 2026

User description

Binding specific releases should work exactly the same as a full release now.
Whether Minor release or Patch release, kick off 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

Builds on #16997 (consolidated pre-release workflow)

Both minor releases and patch releases have the same CI procees:

  • kick off the pre-release workflow
  • give it permission to lock trunk during release
  • merge it
  • give release workflow permission to publish

💥 What does this PR do?

Enable patch releases for individual language bindings
Instead of passing language and version, you pass the exact tagname you want (e.g., selenium-4.28.1-ruby).

Tag format and validation:

  • Validates tags match selenium-4.30.0, selenium-4.28.1-ruby, selenium-4.28.1-python, etc.

Pre-release workflow for patches:

  • Skip Selenium Manager release
  • Skip browser pins, CDP, manager, and multitool updates
  • Run only the relevant language's version/changelog tasks
  • PR body table shows "N/A" for skipped components

Release workflow changes:

  • Filter publish and docs matrix to selected language only
  • Skip GitHub release creation for patch releases
  • Java/dotnet patch releases update existing GitHub release with new artifacts
  • Reset version to nightly for the released language
  • Skip nightly reset and mirror update for patch releases
  • Publish nightly for the released language only

🔧 Implementation Notes

  • Both pre-release and release workflows call the reusable workflow for consistent parsing of tag name

  • Uses full language names in workflow matrix (ruby, python, javascript, java, dotnet)

  • Release workflow extracts tag from PR branch name created by pre-relese workflow

  • Updated release_updates Rake task accepts tag (e.g., selenium-4.28.1-ruby) and runs appropriate tasks

💡 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 e51cd5a

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent numeric comparison failures

In the parse-release-tag.yml workflow, explicitly interpret the patch version
number as base-10 to prevent errors when comparing versions with leading zeros
like 4.0.08.

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

-PATCH=$(echo "$VERSION" | cut -d. -f3)
+PATCH_RAW=$(echo "$VERSION" | cut -d. -f3)
+PATCH=$((10#$PATCH_RAW))
 
 # Extract language suffix
 if [[ "$TAG" =~ -(ruby|python|javascript|java|dotnet)$ ]]; 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 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
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential bash scripting error where version numbers with leading zeros (e.g., 08) are treated as octal, which would cause the workflow to fail.

Low
Learned
best practice
Validate and trim workflow inputs

Trim TAG/INPUT_LANG, fail fast if TAG is empty or the version regex doesn’t
match, and validate that LANG is one of the allowed values before writing
outputs.

.github/workflows/update-documentation.yml [59-70]

 run: |
-  echo "version=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" >> "$GITHUB_OUTPUT"
+  set -euo pipefail
 
-  # Extract language from tag suffix or use input
+  TAG="${TAG//[[:space:]]/}"
+  INPUT_LANG="${INPUT_LANG//[[:space:]]/}"
+
+  if [ -z "$TAG" ]; then
+    echo "::error::Missing tag"
+    exit 1
+  fi
+
+  VERSION="$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)"
+  if [ -z "$VERSION" ]; then
+    echo "::error::Unable to parse version from tag: '$TAG'"
+    exit 1
+  fi
+  echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+
   if [ -n "$INPUT_LANG" ] && [ "$INPUT_LANG" != "all" ]; then
     LANG="$INPUT_LANG"
   elif [[ "$TAG" =~ -(javascript|python|ruby|java|dotnet)$ ]]; then
     LANG="${BASH_REMATCH[1]}"
   else
     LANG="all"
   fi
+
+  case "$LANG" in
+    all|java|python|ruby|dotnet|javascript) ;;
+    *) echo "::error::Invalid language: '$LANG'"; exit 1 ;;
+  esac
   echo "language=$LANG" >> "$GITHUB_OUTPUT"
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Add explicit validation and trimming at integration boundaries (workflow inputs/env) before parsing/using values.

Low
Return immutable parsed results

Normalize the incoming tag (strip whitespace) and return a frozen hash so
callers can’t accidentally mutate the parsed result.

rake_tasks/common.rb [25-40]

 def self.parse_tag(tag)
+  tag = tag&.strip
   pattern = /^selenium-(\d+\.\d+\.\d+)(?:-(#{BINDINGS.join('|')}))?$/
   match = tag&.match(pattern)
   raise "Invalid tag format: #{tag}" unless match
 
   version = match[1]
   language = match[2] || 'all'
   patch = version.split('.')[2].to_i
 
   if patch.positive? && language == 'all'
     raise "Patch releases must specify a language (e.g., selenium-#{version}-ruby)"
   end
   raise 'Full releases (X.Y.0) cannot have a language suffix' if patch.zero? && language != 'all'
 
-  {version: version, language: language, patch: patch}
+  {version: version, language: language, patch: patch}.freeze
 end
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - When returning collection-like data, return an immutable/defensive copy to avoid callers mutating internal state.

Low
  • More

Previous suggestions

Suggestions up to commit dc85c51
CategorySuggestion                                                                                                                                    Impact
Possible issue
Map tag language to task namespace

In rake_tasks/common.rb, map tag languages like ruby to their corresponding Rake
task namespaces like rb to prevent task invocation failures.

rake_tasks/common.rb [16-40]

 module SeleniumRake
   BINDINGS = %w[ruby python javascript java dotnet].freeze
-...
+  LANGUAGE_TASK_MAP = {
+    'ruby' => 'rb',
+    'python' => 'py',
+    'javascript' => 'javascript',
+    'java' => 'java',
+    'dotnet' => 'dotnet',
+    'all' => 'all'
+  }.freeze
+
+  class << self
+    attr_accessor :git
+  end
+
+  # Parse a release tag like "selenium-4.28.0" or "selenium-4.28.1-ruby"
+  # Returns { version:, language:, task_language:, patch: } where language defaults to 'all'
   def self.parse_tag(tag)
     pattern = /^selenium-(\d+\.\d+\.\d+)(?:-(#{BINDINGS.join('|')}))?$/
     match = tag&.match(pattern)
     raise "Invalid tag format: #{tag}" unless match
 
     version = match[1]
     language = match[2] || 'all'
+    task_language = LANGUAGE_TASK_MAP.fetch(language)
     patch = version.split('.')[2].to_i
-...
-    {version: version, language: language, patch: patch}
+
+    if patch.positive? && language == 'all'
+      raise "Patch releases must specify a language (e.g., selenium-#{version}-ruby)"
+    end
+    raise 'Full releases (X.Y.0) cannot have a language suffix' if patch.zero? && language != 'all'
+
+    {version: version, language: language, task_language: task_language, patch: patch}
   end
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where language names from tags (ruby, python) do not match the Rake task namespaces (rb, py), which would cause the script to fail. The proposed fix is accurate and prevents a runtime error.

High
Invoke tasks using mapped namespace

In the Rakefile, use the newly mapped task language (e.g., rb instead of ruby)
when dynamically invoking Rake tasks to ensure they are found and executed
correctly.

Rakefile [106-125]

 task :release_updates, [:tag, :channel] do |_task, arguments|
   parsed = SeleniumRake.parse_tag(arguments[:tag])
   version = parsed[:version]
-  language = parsed[:language]
+  language = parsed[:task_language]
 
   if parsed[:patch].zero?
     Rake::Task['update_browsers'].invoke(arguments[:channel])
     Rake::Task['update_cdp'].invoke(arguments[:channel])
     Rake::Task['update_manager'].invoke
     Rake::Task['update_multitool'].invoke
     Rake::Task['authors'].invoke
     Rake::Task['rust:version'].invoke(version)
     Rake::Task['rust:update'].invoke
     Rake::Task['rust:changelogs'].invoke
   end
 
   Rake::Task["#{language}:version"].invoke(version)
   Rake::Task["#{language}:update"].invoke
   Rake::Task["#{language}:changelogs"].invoke
 end
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly applies the fix from the first suggestion, ensuring that the Rakefile uses the correct task namespace for invoking tasks. It is a necessary change to fix the identified bug.

High
Skip non-target matrix jobs

In the publish job of the release.yml workflow, move the conditional logic from
the run step to a job-level if condition. This will completely skip unnecessary
matrix jobs, saving CI resources and preventing potential artifact-related
errors.

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

 publish:
   name: Build and Publish ${{ matrix.language }}
   needs: [parse-tag, get-approval]
+  if: ${{ needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == matrix.language }}
   strategy:
     fail-fast: false
     matrix:
       language: [java, python, ruby, dotnet, javascript]
   uses: ./.github/workflows/bazel.yml
   with:
     name: Publish ${{ matrix.language }}
     gpg-sign: ${{ matrix.language == 'java' }}
-    run: ${{ (needs.parse-tag.outputs.language == 'all' || needs.parse-tag.outputs.language == matrix.language) && format('./go {0}:release', matrix.language) || 'echo skipping' }}
+    run: ./go ${{ matrix.language }}:release
     artifact-name: release-packages-${{ matrix.language }}
     artifact-path: build/dist/*.*
   secrets: inherit
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that using a job-level if is more efficient and robust than conditionally running echo skipping within a step. This improves the CI workflow's performance and reliability.

Medium
Learned
best practice
Validate and trim extracted inputs

Trim and validate TAG and ensure the PR branch actually starts with
release-preparation- so malformed/empty inputs fail deterministically with a
clear error before calling downstream workflows.

.github/workflows/release.yml [30-43]

 - name: Extract tag from input or PR branch
   id: extract
   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"
+    set -euo pipefail
+
+    if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
+      TAG="${INPUT_TAG//[[:space:]]/}"
     else
-      # Extract tag from branch name: release-preparation-selenium-4.28.1-ruby -> selenium-4.28.1-ruby
-      TAG="${PR_HEAD_REF#release-preparation-}"
+      case "$PR_HEAD_REF" in
+        release-preparation-*) TAG="${PR_HEAD_REF#release-preparation-}" ;;
+        *) echo "::error::Unexpected PR branch name: '$PR_HEAD_REF'"; exit 1 ;;
+      esac
     fi
+
+    TAG="${TAG//[[:space:]]/}"
+    if [ -z "$TAG" ]; then
+      echo "::error::Missing release tag"
+      exit 1
+    fi
+
     echo "tag=$TAG" >> "$GITHUB_OUTPUT"
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Add explicit validation and trimming/emptiness guards at CI/context boundaries before using inputs.

Low
Suggestions up to commit 9d66c84
CategorySuggestion                                                                                                                                    Impact
Learned
best practice
Centralize repeated regex constants

Define the allowed language alternation once (e.g., LANG_RE) and reuse it in all
regex checks/extractions to prevent mismatches when the list changes.

.github/workflows/parse-release-tag.yml [39-54]

+LANG_RE='ruby|python|javascript|java|dotnet'
+
 # Validate tag format: selenium-X.Y.Z or selenium-X.Y.Z-lang
-if [[ ! "$TAG" =~ ^selenium-[0-9]+\.[0-9]+\.[0-9]+(-(ruby|python|javascript|java|dotnet))?$ ]]; then
+if [[ ! "$TAG" =~ ^selenium-[0-9]+\.[0-9]+\.[0-9]+(-(${LANG_RE}))?$ ]]; then
   echo "::error::Invalid tag format: '$TAG'. Expected selenium-X.Y.Z or selenium-X.Y.Z-lang (ruby, python, javascript, java, dotnet)"
   exit 1
 fi
 
 # Extract version
-VERSION=$(echo "$TAG" | sed -E 's/^selenium-([0-9]+\.[0-9]+\.[0-9]+)(-(ruby|python|javascript|java|dotnet))?$/\1/')
+VERSION=$(echo "$TAG" | sed -E "s/^selenium-([0-9]+\.[0-9]+\.[0-9]+)(-(${LANG_RE}))?$/\1/")
 PATCH=$(echo "$VERSION" | cut -d. -f3)
 
 # Extract language suffix
-if [[ "$TAG" =~ -(ruby|python|javascript|java|dotnet)$ ]]; then
+if [[ "$TAG" =~ -(${LANG_RE})$ ]]; then
   LANG_SUFFIX="${BASH_REMATCH[1]}"
 else
   LANG_SUFFIX=""
 fi
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Reduce duplication by centralizing shared constants/logic (e.g., language lists/regexes) instead of repeating them.

Low
Normalize and validate input early

Normalize and validate tag up-front (string coercion, strip, and empty check) so
whitespace/nil inputs fail deterministically and error messages are clearer.
Also avoid relying on positional indexing into split without guarding.

rake_tasks/common.rb [25-40]

 def self.parse_tag(tag)
+  tag = tag.to_s.strip
+  raise 'Missing required tag' if tag.empty?
+
   pattern = /^selenium-(\d+\.\d+\.\d+)(?:-(#{BINDINGS.join('|')}))?$/
-  match = tag&.match(pattern)
+  match = tag.match(pattern)
   raise "Invalid tag format: #{tag}" unless match
 
   version = match[1]
   language = match[2] || 'all'
-  patch = version.split('.')[2].to_i
+  patch = version.split('.').fetch(2).to_i
 
   if patch.positive? && language == 'all'
     raise "Patch releases must specify a language (e.g., selenium-#{version}-ruby)"
   end
   raise 'Full releases (X.Y.0) cannot have a language suffix' if patch.zero? && language != 'all'
 
   {version: version, language: language, patch: patch}
 end
Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Add explicit validation and input normalization at integration boundaries (trim/guard nil/blank) before parsing user-provided values.

Low
Suggestions up to commit 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/*.*
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
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
-
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##*.}"
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
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
✅ 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 }}

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.

@titusfortner
Copy link
Member Author

Did a major rework off of other recently merged branches

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 5 out of 5 changed files in this pull request and generated 3 comments.

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 5 out of 5 changed files in this pull request and generated 13 comments.

titusfortner and others added 3 commits January 24, 2026 21:43
Add support for releasing individual language bindings with tags like
selenium-4.28.1-ruby. Patch releases (version X.Y.Z where Z > 0) require
a language suffix and skip:
- Selenium Manager/Rust release
- Browser, CDP, manager, and multitool updates
- GitHub release creation
- Nightly reset and mirror update

Full releases (X.Y.0) continue to release all languages and components.

Creates a reusable parse-release-tag.yml workflow to validate tags and
extract version/language information for both pre-release and release
workflows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
titusfortner and others added 6 commits January 24, 2026 21:43
- Add BINDINGS constant with valid language names
- Add parse_tag method to validate and parse release tags
- Update release_updates task to accept tag and handle both full/patch releases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use grouped redirects in parse-release-tag.yml (SC2129)
- Use bash parameter expansion instead of sed (SC2001)
- Use conditional run expression instead of job-level if with matrix context
- Remove matrix filter from docs job (let all run, only relevant ones have updates)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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 1 comment.

@titusfortner titusfortner merged commit 690992d into trunk Jan 25, 2026
30 checks passed
@titusfortner titusfortner deleted the patch_release branch January 25, 2026 04:37
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