Skip to content

[build] recover from incomplete Java release by dropping staged repositories when found - #18112

Merged
titusfortner merged 2 commits into
SeleniumHQ:trunkfrom
titusfortner:java-staging-cleanup
Oct 1, 2026
Merged

titusfortner merged 2 commits into
SeleniumHQ:trunkfrom
titusfortner:java-staging-cleanup

Conversation

@titusfortner

Copy link
Copy Markdown
Member

💥 What does this PR do?

  • java:release now drops a staging repository left behind by an earlier CI attempt and redeploys, instead of aborting. Since repositories only apply to given credentials, this code can't see attempts from local executions, so there is no value to just aborting.

🔧 Implementation Notes

  • Open repositories never reached the Portal, so they are dropped outright. Closed ones are looked up at the Portal first: a FAILED deployment is dropped, a PUBLISHED one for this version ends the deploy as already done, and anything still in flight aborts naming the state, since the right move is to rerun once it settles.
  • Deployments are matched to the requested version by their purls, so a stale deployment of an older version cannot suppress the current release.
  • Verified the decision table with stubbed API responses: open, released, and closed in each Portal state, for this version and another.

🤖 AI assistance

  • AI assisted (complete below)
    • Tool(s): Claude Code (Fable 5.1)
    • What was generated: the release failure diagnosis, the fix, and this description
    • I reviewed all AI output and can explain the change

🔄 Types of changes

  • Bug fix (backwards compatible)

@selenium-ci selenium-ci added the B-build Includes scripting, bazel and CI integrations label Oct 1, 2026
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Recover stale Java staging repositories before deployment

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Drop abandoned staging repositories so a previous CI attempt does not block a Java release.
• Check closed repositories in the Portal; skip published versions and defer while deployment
 continues.
• Read Maven credentials from the matching server entry in settings.xml.
Diagram

graph TD
  A["Java release"] --> B{"Already published?"} -->|no| C["Repository lookup"] --> D{"Repository state"} -->|closed| E{"Portal status"} -->|failed| F["Drop repository"] --> G["Deploy artifacts"]
  B -->|yes| H["Stop release"]
  C -->|none| G
  D -->|open| F
  D -->|released| G
  E -->|published or in flight| H
  E -->|other version| G
Loading
High-Level Assessment

Using the staging API for cleanup and the Portal for closed deployments fits the existing release workflow. Retaining the blanket abort would continue blocking recoverable CI attempts; dropping every repository without a Portal check could interrupt a deployment still in flight. The approach relies on the stated serialization of release runs.

Files changed (1) +45 / -28

Bug fix (1) +45 / -28
java.rakeRecover stale Sonatype staging state during Java release +45/-28

Recover stale Sonatype staging state during Java release

• Parses the central server entry in Maven settings for credentials. Adds staging-repository deletion and shared Portal status/version checks. The pre-deploy check now drops open or failed repositories, skips a matching published deployment, and aborts when a matching closed deployment remains in flight.

rake_tasks/java.rake

@qodo-code-review

qodo-code-review Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Concurrent Java releases can delete uploads ✗ Dismissed
Description
leftover_published? sends every open repository to drop_repository without checking whether
another release is still uploading to it. Two releases using the shared credentials can overlap, so
the second can delete the first release’s repository between its artifact uploads and
trigger_publish.
Code

rake_tasks/java.rake[R198-199]

+    puts "Dropping staging repository #{repo['key']} left #{repo['state']} by a previous attempt"
+    drop_repository(repo['key'])
Evidence
The search includes repositories across runners, and the new open-state path deletes them.
Publishing uploads artifacts after the check and before triggering publication; the release workflow
has no concurrency group, while release jobs use shared Maven secrets.

rake_tasks/java.rake[140-147]
rake_tasks/java.rake[183-200]
rake_tasks/java.rake[370-389]
.github/workflows/release.yml[1-17]
.github/workflows/bazel.yml[87-99]

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

## Issue description
An open staging repository can belong to a concurrently running Java release; deleting it destroys that release’s upload.
## Fix Focus Areas
- rake_tasks/java.rake[183-200]
- rake_tasks/java.rake[177-180]
## Recommended Fix
Do not treat every visible open repository as abandoned. Establish release ownership or enforce a lock shared by all Java release entry points before deleting open repositories.

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


2. Java release cleanup lacks tests 📘 Rule violation ≡ Correctness
Description
leftover_published? adds repository deletion and Portal-state decisions without a checked-in
focused test for those branches. An empty purls list passes for_version? for any requested
version, so a test of that case would also catch a closed repository being treated as a match for
the wrong release.
Code

rake_tasks/java.rake[R189-191]

+      status = portal_status(repo['portal_deployment_id'])
+      return false unless for_version?(status, version)
+      return true if status['deploymentState'] == 'PUBLISHED'
Evidence
Rule 4 requires appropriate focused tests for behavioral changes. The changed code makes deletion
and release decisions using for_version?, whose empty-list branch accepts any version; a
repository search found no tests referencing Sonatype.

AGENTS.md: Use Focused Tests Without Misleading Mocks
rake_tasks/java.rake[172-200]
rake_tasks/java.rake[377-384]

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

## Issue description
The new Java release cleanup has no checked-in tests for its Portal-state and version decisions. An empty `purls` list currently matches every version.

## Fix Focus Areas
- rake_tasks/java.rake[172-200]

## Recommended Fix
Add focused tests with stubbed HTTP responses covering open and closed repositories, Portal states, matching and nonmatching versions, and empty `purls`. Make the version decision reject unverified matches before testing deletion.

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

Dismiss ↗ | View ↗



Remediation recommended

3. Another version can suppress a release 🐞 Bug ≡ Correctness
Description
for_version? treats missing or empty purls as a match for every requested version. When a closed
repository’s Portal status is PUBLISHED without PURLs, leftover_published? returns true and
java:release skips publishing the requested version without establishing that the deployment
contains it.
Code

rake_tasks/java.rake[R173-174]

+    purls = status.fetch('purls', [])
+    purls.empty? || purls.any? { |purl| purl.end_with?("@#{version}") }
Evidence
The new predicate explicitly accepts an empty PURL list, and the closed-repository path uses that
predicate before returning true for PUBLISHED. The release task exits when already_deployed?
returns true. The Portal API documents PURLs as the package coordinates in a status response, but
does not establish that every response must contain them.

rake_tasks/java.rake[172-180]
rake_tasks/java.rake[185-192]
rake_tasks/java.rake[378-383]
🌐 The status API example identifies published package coordinates using the purls field.

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

## Issue description
An empty PURL list acts as a wildcard when deciding whether an earlier deployment already published the requested version.
## Fix Focus Areas
- rake_tasks/java.rake[172-175]
- rake_tasks/java.rake[185-195]
## Recommended Fix
Require a PURL containing the requested version before a closed repository can suppress the release. Treat missing version evidence as indeterminate rather than already published.

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

Dismiss ↗ | View ↗


4. Closed repositories can block release retries ✗ Dismissed
Description
leftover_published? calls portal_status for a closed repository even when portal_deployment_id
is absent. A repository closed before it receives a Portal deployment ID produces a status request
with an empty id, which fails before the release can clean up or redeploy.
Code

rake_tasks/java.rake[R188-189]

+    if repo['state'] == 'closed'
+      status = portal_status(repo['portal_deployment_id'])
Evidence
The closed-state branch passes the unvalidated field into a URL and request_json raises on
unsuccessful responses. An OSSRH staging API publishing workflow explicitly waits for
portal_deployment_id to cease being null, demonstrating that a repository can be present before
its ID is available.

rake_tasks/java.rake[165-169]
rake_tasks/java.rake[185-195]
rake_tasks/java.rake[131-137]
🌐 The workflow polls the staging search response while the repository’s portal_deployment_id is null.

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

## Issue description
A closed staging repository may not yet have a Portal deployment ID, but cleanup queries the Portal with that missing ID.
## Fix Focus Areas
- rake_tasks/java.rake[185-200]
## Recommended Fix
Check for a deployment ID before querying status. Handle the no-ID state explicitly, distinguishing an upload still in progress from a repository safe to drop.

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


Grey Divider

Context sources
✅ Web pages:
  +7 more
Review mode: 🧠 Deep: This push combines a substantial test-driver lifecycle/concurrency refactor across many independent paths with behavior-changing Sonatype deployment logic, creating a high density of subtle, independently missable defects.

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Findings visible per group, which tucks the rest behind a View link

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 82ff791

Results up to commit c6f86a3 ⚖️ Balanced


🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Java release cleanup lacks tests 📘 Rule violation ≡ Correctness
Description
leftover_published? adds repository deletion and Portal-state decisions without a checked-in
focused test for those branches. An empty purls list passes for_version? for any requested
version, so a test of that case would also catch a closed repository being treated as a match for
the wrong release.
Code

rake_tasks/java.rake[R189-191]

+      status = portal_status(repo['portal_deployment_id'])
+      return false unless for_version?(status, version)
+      return true if status['deploymentState'] == 'PUBLISHED'
Evidence
Rule 4 requires appropriate focused tests for behavioral changes. The changed code makes deletion
and release decisions using for_version?, whose empty-list branch accepts any version; a
repository search found no tests referencing Sonatype.

AGENTS.md: Use Focused Tests Without Misleading Mocks
rake_tasks/java.rake[172-200]
rake_tasks/java.rake[377-384]

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

## Issue description
The new Java release cleanup has no checked-in tests for its Portal-state and version decisions. An empty `purls` list currently matches every version.

## Fix Focus Areas
- rake_tasks/java.rake[172-200]

## Recommended Fix
Add focused tests with stubbed HTTP responses covering open and closed repositories, Portal states, matching and nonmatching versions, and empty `purls`. Make the version decision reject unverified matches before testing deletion.

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

Dismiss ↗ | View ↗


2. Concurrent Java releases can delete uploads ✗ Dismissed
Description
leftover_published? sends every open repository to drop_repository without checking whether
another release is still uploading to it. Two releases using the shared credentials can overlap, so
the second can delete the first release’s repository between its artifact uploads and
trigger_publish.
Code

rake_tasks/java.rake[R198-199]

+    puts "Dropping staging repository #{repo['key']} left #{repo['state']} by a previous attempt"
+    drop_repository(repo['key'])
Evidence
The search includes repositories across runners, and the new open-state path deletes them.
Publishing uploads artifacts after the check and before triggering publication; the release workflow
has no concurrency group, while release jobs use shared Maven secrets.

rake_tasks/java.rake[140-147]
rake_tasks/java.rake[183-200]
rake_tasks/java.rake[370-389]
.github/workflows/release.yml[1-17]
.github/workflows/bazel.yml[87-99]

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

## Issue description
An open staging repository can belong to a concurrently running Java release; deleting it destroys that release’s upload.
## Fix Focus Areas
- rake_tasks/java.rake[183-200]
- rake_tasks/java.rake[177-180]
## Recommended Fix
Do not treat every visible open repository as abandoned. Establish release ownership or enforce a lock shared by all Java release entry points before deleting open repositories.

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



Remediation recommended
3. Another version can suppress a release 🐞 Bug ≡ Correctness
Description
for_version? treats missing or empty purls as a match for every requested version. When a closed
repository’s Portal status is PUBLISHED without PURLs, leftover_published? returns true and
java:release skips publishing the requested version without establishing that the deployment
contains it.
Code

rake_tasks/java.rake[R173-174]

+    purls = status.fetch('purls', [])
+    purls.empty? || purls.any? { |purl| purl.end_with?("@#{version}") }
Evidence
The new predicate explicitly accepts an empty PURL list, and the closed-repository path uses that
predicate before returning true for PUBLISHED. The release task exits when already_deployed?
returns true. The Portal API documents PURLs as the package coordinates in a status response, but
does not establish that every response must contain them.

rake_tasks/java.rake[172-180]
rake_tasks/java.rake[185-192]
rake_tasks/java.rake[378-383]
🌐 The status API example identifies published package coordinates using the purls field.

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

## Issue description
An empty PURL list acts as a wildcard when deciding whether an earlier deployment already published the requested version.
## Fix Focus Areas
- rake_tasks/java.rake[172-175]
- rake_tasks/java.rake[185-195]
## Recommended Fix
Require a PURL containing the requested version before a closed repository can suppress the release. Treat missing version evidence as indeterminate rather than already published.

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

Dismiss ↗ | View ↗


4. Closed repositories can block release retries ✗ Dismissed
Description
leftover_published? calls portal_status for a closed repository even when portal_deployment_id
is absent. A repository closed before it receives a Portal deployment ID produces a status request
with an empty id, which fails before the release can clean up or redeploy.
Code

rake_tasks/java.rake[R188-189]

+    if repo['state'] == 'closed'
+      status = portal_status(repo['portal_deployment_id'])
Evidence
The closed-state branch passes the unvalidated field into a URL and request_json raises on
unsuccessful responses. An OSSRH staging API publishing workflow explicitly waits for
portal_deployment_id to cease being null, demonstrating that a repository can be present before
its ID is available.

rake_tasks/java.rake[165-169]
rake_tasks/java.rake[185-195]
rake_tasks/java.rake[131-137]
🌐 The workflow polls the staging search response while the repository’s portal_deployment_id is null.

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

## Issue description
A closed staging repository may not yet have a Portal deployment ID, but cleanup queries the Portal with that missing ID.
## Fix Focus Areas
- rake_tasks/java.rake[185-200]
## Recommended Fix
Check for a deployment ID before querying status. Handle the no-ID state explicitly, distinguishing an upload still in progress from a repository safe to drop.

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


Grey Divider

Qodo Logo

Comment thread rake_tasks/java.rake Outdated
Comment thread rake_tasks/java.rake
Comment thread rake_tasks/java.rake
Comment thread rake_tasks/java.rake
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 82ff791

@titusfortner titusfortner changed the title [build] drop a dead staging repository before the Java deploy and defer to the Portal when a previous deployment is still in flight [build] recover from incomplete Java release by dropping staged repositories when found Oct 1, 2026
@titusfortner
titusfortner merged commit 68d2843 into SeleniumHQ:trunk Oct 1, 2026
23 checks passed
This was referenced Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B-build Includes scripting, bazel and CI integrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants