Skip to content

fix: publish releases automatically to Maven Central - #44

Merged
juherr merged 3 commits into
mainfrom
juherr/fix-central-autopublish
Jul 25, 2026
Merged

fix: publish releases automatically to Maven Central#44
juherr merged 3 commits into
mainfrom
juherr/fix-central-autopublish

Conversation

@juherr

@juherr juherr commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Context

The 0.1.0 release run (30162233461) failed at Wait for Maven Central resolution. The deployment had to be published by hand from the Sonatype portal.

The publishing step logged Deployment will require manual publishing and Waiting until Deployment ... is validated, despite the workflow passing -DautoPublish=true -DwaitUntil=published. The root POM binds the plugin through an explicit <configuration>:

<autoPublish>${central.autoPublish}</autoPublish>   <!-- default false -->
<waitUntil>${central.waitUntil}</waitUntil>         <!-- default validated -->

An explicit plugin <configuration> takes precedence over the mojo's own ${autoPublish} / ${waitUntil} user properties, so both CLI flags were silently ignored. The deployment stopped at VALIDATED, and the next step spent its whole 10-minute budget waiting for a publication that had never been triggered.

Changes

  • Publish step: pass -Dcentral.autoPublish=true -Dcentral.waitUntil=published, the properties the POM actually reads. The central.* indirection is kept on purpose — the safe defaults (false / validated) protect local builds, and only the release workflow opts in.
  • Input validation: assert on the effective POM that autoPublish resolves to true and waitUntil to published. This would have failed the release before the upload instead of ten minutes after it, and guards against a future property rename.
  • Resolution window: 30 → 60 attempts and 20s → 30s sleeps (10 min → 30 min). Propagation to repo1.maven.org took ~15 minutes for 0.1.0, over the previous budget. The failure message now points at the deployments page and at the re-run procedure.
  • CONTRIBUTING.md: document the property indirection trap, and prefer gh run rerun <id> --failed over a fresh dispatch when resuming a failed release — a re-run keeps the original commit, so the signed tag still points at the code that produced the published artifacts.

pom.xml is unchanged: the indirection is correct, the caller was wrong.

Verification

The diagnosis is proven by a positive/negative pair on the effective POM:

$ ./mvnw -q -N -Prelease -Dcentral.autoPublish=true -Dcentral.waitUntil=published help:effective-pom -Doutput=/tmp/new.xml
<autoPublish>true</autoPublish>
<waitUntil>published</waitUntil>

$ ./mvnw -q -N -Prelease -DautoPublish=true -DwaitUntil=published help:effective-pom -Doutput=/tmp/old.xml
<autoPublish>false</autoPublish>
<waitUntil>validated</waitUntil>

The second block is what the workflow was doing until now. actionlint passes on the modified workflow.

0.1.0 itself was finalized separately by re-running the failed job: the signed tag 0.1.0 now points at 4aa373d (GPG signature verified), the GitHub Release is published, and the release-smoke consumer builds against repo1.maven.org. The end-to-end effect of this fix can only be confirmed on the next release.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened Maven Central release validation by checking the effective release configuration before any uploads start.
    • Updated publish/deploy flags for consistent auto-publish and “wait until published” behavior.
    • Improved Maven Central resolution polling by verifying expected published artifacts and extending the wait window with clearer failure guidance.
  • Documentation

    • Updated release workflow instructions to resume failed runs using re-runs without changing the original commit.
    • Documented the publishing configuration behavior and the pre-upload validation step.

The release workflow passed `-DautoPublish=true -DwaitUntil=published`, but
the root POM binds the plugin through an explicit `<configuration>` block
referencing `${central.autoPublish}` and `${central.waitUntil}`. An explicit
plugin configuration takes precedence over the mojo's own user properties, so
both flags were silently ignored. The 0.1.0 deployment stopped at `VALIDATED`
and required manual publishing from the Sonatype portal.

Pass the properties the POM actually reads, and assert on the effective POM
during input validation so a future rename fails the release before anything
is uploaded rather than ten minutes later.

Widen the resolution window to 30 minutes as well: propagation to
repo1.maven.org took roughly 15 minutes for 0.1.0, over the previous
10-minute budget.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@juherr, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2711b637-650c-4fd5-805c-a94270abea15

📥 Commits

Reviewing files that changed from the base of the PR and between d3100e8 and 53a910b.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • scripts/check-central-release.sh
📝 Walkthrough

Walkthrough

The release workflow validates Maven Central publishing settings before upload, uses the central.* deployment properties, and checks all expected artifacts for up to 60 attempts. Contributor documentation adds failed-run recovery and plugin configuration guidance.

Changes

Maven Central release flow

Layer / File(s) Summary
Release validation and configuration guidance
.github/workflows/release.yml, CONTRIBUTING.md
The release profile’s effective POM is checked for autoPublish=true and waitUntil=published; documentation explains plugin configuration precedence.
Central deployment and artifact resolution
.github/workflows/release.yml, scripts/check-central-release.sh
Deployment uses central.autoPublish and central.waitUntil; resolution polling checks every expected payload for up to 60 attempts with 30-second delays before smoke verification.
Failed-release rerun procedure
CONTRIBUTING.md
Documentation describes rerunning failed jobs on the original commit and repeating remaining release steps after artifact resolution.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReleaseWorkflow
  participant MavenCentralPlugin
  participant CentralRepository
  participant ReleaseChecker
  ReleaseWorkflow->>MavenCentralPlugin: Deploy with central.autoPublish and central.waitUntil
  MavenCentralPlugin->>CentralRepository: Publish release artifacts
  ReleaseWorkflow->>ReleaseChecker: Check expected artifact payloads
  ReleaseChecker->>CentralRepository: Send timed HEAD requests
  CentralRepository-->>ReleaseChecker: Return payload availability
  ReleaseChecker-->>ReleaseWorkflow: Return resolution status
Loading

Possibly related PRs

  • juherr/datex4j#43: Both PRs update Maven Central publication properties and artifact availability checks.

Poem

A rabbit checks the POM just right,
Then hops through Central’s publishing night.
Sixty checks beneath the moon,
Awaiting “published” very soon.
If the run should lose its way,
Rerun the failed hops another day.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: making Maven Central releases publish automatically.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch juherr/fix-central-autopublish

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 187-193: Update the release artifact availability check in
.github/workflows/release.yml#L157-L193 to HEAD each expected artifact using its
actual filename and require every published file, including JARs and POMs, to
resolve before treating the release as complete; revise the timeout messages and
rerun guidance there to describe all expected files rather than only Maven
Central POM resolution. Update CONTRIBUTING.md#L173-L176 to match this
all-artifacts-resolvable rerun condition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a63f27c-044a-49b7-b495-5bd152f13d4d

📥 Commits

Reviewing files that changed from the base of the PR and between 4aa373d and 9e11ddc.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • CONTRIBUTING.md

Comment thread .github/workflows/release.yml
The Central availability check only requested the `.pom` of each entry in
`config/release/public-artifacts.txt`, ignoring the packaging column. A release
whose POMs had propagated but whose jars had not would satisfy the check, so a
re-run would skip the deploy and freeze an incomplete release — the one path
that cannot be recovered by re-running.

Extract the check into `scripts/check-central-release.sh` and assert the same
payload set that `verify-central-bundle.sh` already asserts locally: each POM,
plus the main, sources, and javadoc jars for `jar` packaging. Both the deploy
guard and the resolution wait now share it, so they cannot drift apart.

Co-Authored-By: Claude <noreply@anthropic.com>
@juherr

juherr commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

Addressed in d3100e8 — the finding is valid.

config/release/public-artifacts.txt carries a packaging column that the remote check read into _packaging and never used, while scripts/verify-central-bundle.sh asserts pom + main jar + sources + javadoc for jar packaging on the local bundle. The two checks had drifted apart.

The consequence is worse than a premature success: the POM-only check also gates the deploy skip. A release whose POMs had propagated but whose jars had not would satisfy it, so a re-run would skip the deploy and freeze the incomplete release — the one failure mode a re-run cannot recover from.

Extracted into scripts/check-central-release.sh, asserting the same payload set as the local bundle verification. Both the deploy guard and the resolution wait now call it, so they cannot drift again.

Verified against the live repository:

$ scripts/check-central-release.sh 0.1.0          # published
exit=0
$ scripts/check-central-release.sh 9.9.9          # absent
Not resolvable from Maven Central: .../datex4j/9.9.9/datex4j-9.9.9.pom
exit=1

And the partial-propagation case you described, against a local repository serving only the POMs:

$ CENTRAL_BASE_URL=http://127.0.0.1:8765/dev/juherr/datex4j scripts/check-central-release.sh 1.0.0
Not resolvable from Maven Central: .../datex4j-core/1.0.0/datex4j-core-1.0.0.jar
exit=1

CONTRIBUTING.md now states the re-run condition as "every published file resolves", not "every artifact". actionlint and shellcheck pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check-central-release.sh`:
- Around line 16-23: Update the curl status handling in check-central-release.sh
to distinguish a genuine 404/missing artifact from Central, DNS/TLS, timeout,
and other transport or server failures. Return the existing “not published”
status only for 404 responses; propagate or return a distinct failure status for
all other errors so release.yml cannot trigger deploy during a Central outage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 39e23992-e0b3-4111-a328-504775c0e7f1

📥 Commits

Reviewing files that changed from the base of the PR and between 9e11ddc and d3100e8.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • CONTRIBUTING.md
  • scripts/check-central-release.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • CONTRIBUTING.md
  • .github/workflows/release.yml

Comment thread scripts/check-central-release.sh Outdated
The availability check discarded curl's status, so a DNS failure, a timeout, or
a 5xx from Maven Central was indistinguishable from a 404. The deploy guard
would then read "unreachable" as "not published" and redeploy a version that
may already exist.

Separate the two: exit 1 means Central answered and something is absent, exit 2
means Central could not be questioned. The deploy guard refuses to publish on 2
rather than guessing, while the resolution loop retries it — that loop only
waits, so an unreachable Central is just another retry there.

Co-Authored-By: Claude <noreply@anthropic.com>
@juherr

juherr commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

Addressed in 53a910b — this one is valid too, and the failure mode is the dangerous direction.

resolves() discarded curl's exit status, so a DNS failure, a connection timeout, or a 5xx all looked exactly like a 404. Reading "unreachable" as "not published" makes the deploy guard act: it would republish a version that may already exist on Central.

The check now separates the two outcomes:

exit meaning
0 every payload resolves
1 Central answered, something is absent
2 Central could not be questioned (transport failure or unexpected HTTP status)

The two callers deliberately differ on 2:

  • Deploy guard — refuses to publish and fails the step. Guessing here is what causes a double deploy.
  • Resolution loop — retries. That loop only waits, so an unreachable Central is just another retry; failing the whole release on one flaky request would be worse.

Verified all four states:

$ scripts/check-central-release.sh 0.1.0                                  # published
exit=0
$ scripts/check-central-release.sh 9.9.9                                  # absent
Not published to Maven Central: .../datex4j-9.9.9.pom
exit=1
$ CENTRAL_BASE_URL=http://127.0.0.1:1/... scripts/check-central-release.sh 0.1.0   # refused
Maven Central is unreachable (curl exit 7): ...
exit=2
$ CENTRAL_BASE_URL=https://no-such-host.invalid/x ...                     # DNS
Maven Central is unreachable (curl exit 6): ...
exit=2
$ CENTRAL_BASE_URL=http://127.0.0.1:8766/... ...                          # local server returning 503
Unexpected HTTP 503 from Maven Central: ...
exit=2

And the deploy guard's decision for each:

published     -> DECISION: skip deploy
absent        -> DECISION: deploy
unreachable   -> DECISION: refuse (exit 2)

actionlint and shellcheck pass.

@juherr
juherr merged commit c8e772e into main Jul 25, 2026
8 checks passed
@juherr
juherr deleted the juherr/fix-central-autopublish branch July 25, 2026 15:46
juherr added a commit that referenced this pull request Jul 25, 2026
fix: publish releases automatically to Maven Central
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant