CI: make the GitHub release runnable on its own - #533
Conversation
The steps that create the GitHub release were inline in `release.yml`, a workflow that cannot be started without also running its tag and publish jobs. Once Maven Central has accepted a version it cannot be republished, so a release that reaches Central without a GitHub release has no way back. zenoh-kotlin hit that case with 1.10.0 and fixed it in #708; this is the same change here. The steps move to `.github/workflows/release-github.yml`, which `release.yml` calls as its last job and which you can also dispatch from the Actions tab. Three checks guard a manual run: `version` and `branch` are constrained to plain ref characters before `eclipse-zenoh/ci/publish-crates-github` builds a shell command from them; `version.txt` at the tag must name the version being released; and `check-maven` requires the version to resolve from Maven Central, which is what rejects a rehearsal tag. `publish-dokka.yml` declared a manual trigger with no inputs, so a dispatch received an empty `inputs`: the deploy step was skipped and the job built the documentation site and discarded it, reporting success. It now declares `live-run` and a required `branch`. Two gates come with this. `release.yml` gains `github_release`, and both the release and the documentation deploy are gated on `maven_publish` as well, so a live run with uploads disabled announces nothing. The `tag` job now refuses `live-run` with `maven_publish` off outright — that combination force-pushed the real tag while publishing nothing.
milyin
left a comment
There was a problem hiding this comment.
I re-reviewed the complete workflow/documentation change and checked it against the rewritten PR description. The extraction, job dependencies, Maven/tag checks, and recovery instructions are internally consistent, but I found one unresolved safety issue and a few claims that need correction.
-
The input regex does not close the shared action's shell-injection boundary. It protects
versionandbranch, butpublish-crates-githubalso obtains the latest release'stagName, appends it as--notes-start-tag, joins the entire argument list into one string, and passes that string tosh(..., { shell: true })withBOT_TOKEN_WORKFLOWin the environment. That tag is not validated here. Git accepts shell-bearing tag names (for example,git check-ref-formatacceptsrefs/tags/x$(id),x;id,x|id,x&whoami, andx>pwn), so a crafted latest release remains a command-injection source even when the two workflow inputs pass this regex. This matters more now that the bot-backed action is directly dispatchable. Please either fix/use the upstream argument-array implementation before enabling this path, or implement the release command locally without a shell. The PR description's statement that the constraint “closes it here” is currently too strong. -
The prose contradicts the implementation and the upstream issue. The PR first says the regex works around the first defect, then says “None of the three is worked around here.” The workflow and
PUBLISHING.mddescribe “two” remaining defects and say neither affects a normal release, while the same action compiles/installstoml-cli2on its normal path; that is an operational dependency and roughly 40-second cost on ordinary releases. Please distinguish the locally attempted safety mitigation from the two unmitigated operational/notes defects. -
“Builds nothing” / “contains no Rust” are inaccurate as written. The shared action builds the Rust helper, so the Ubuntu-runner rationale should be that nothing requires macOS, not that the job builds nothing. Also, this repository does contain the pin-only Rust crate (
Cargo.tomlandci/pin.rs); the accurate publishing claim is that the released SDK artifacts contain no Rust/native code and this repository builds no native libraries.
Validation performed on head f0ec0e0: all workflow YAML parses, git diff --check is clean, markdownlint-cli2 PUBLISHING.md passes, the Maven/tag/release facts cited in the docs match the current public state, and all PR checks are green.
— Codex (GPT-5)
There was a problem hiding this comment.
Pull request overview
Adds standalone recovery workflows for GitHub releases and documentation deployments while preventing announcements when Maven publication is skipped.
Changes:
- Extracts GitHub release creation into a reusable, manually dispatchable workflow.
- Adds release gating and manual Dokka deployment inputs.
- Documents release modes and recovery procedures.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
.github/workflows/release.yml |
Adds release gating and delegates GitHub release creation. |
.github/workflows/release-github.yml |
Implements guarded standalone GitHub release recovery. |
.github/workflows/publish-dokka.yml |
Enables manual documentation deployment. |
PUBLISHING.md |
Documents release and recovery procedures. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| branch: | ||
| description: Ref to build the documentation from - use refs/tags/<version> for a released version | ||
| type: string | ||
| required: true |
| # Two known upstream defects are tracked in eclipse-zenoh/ci#470; neither | ||
| # affects a normal release. They are deliberately not worked around here: | ||
| # a fix upstream reaches every Zenoh repository, one here reaches this | ||
| # repository only. |
| Two upstream defects are known and tracked in | ||
| [eclipse-zenoh/ci#470](https://github.com/eclipse-zenoh/ci/issues/470). Neither | ||
| affects a normal release, and both are left upstream deliberately — a fix there | ||
| reaches every Zenoh repository, whereas working around them here would fix one. |
| a GitHub release or the documentation for a version already on Central, use the | ||
| standalone workflows under [If a step after Maven Central | ||
| fails](#if-a-step-after-maven-central-fails), which verify rather than assume. |
Three comments in the extracted workflow said things zenoh-kotlin's copy does not, and one of them was wrong: the shared action installs a Rust helper before it runs `gh`, so the job does not "build nothing". All three go, leaving only the differences the repository forces - the coordinate checked on Maven Central, this repository's rehearsal tag, and why the steps moved. PUBLISHING.md picks up the hunk of #708 that was missed: this repository holds `zenoh-flat-jni-pin` (`Cargo.toml` and `ci/pin.rs`), so the claim that it contains no Rust was wrong. What the released artifacts contain is unchanged - no Rust, no native library.
|
Thanks — all three points looked at. The governing constraint on what changed: this pull request exists to give zenoh-java the same release machinery zenoh-kotlin merged in #708, so a difference between the two repositories has to earn its place. 1. The regex does not close the injection boundary — not fixed here, deliberately. You are right that it does not: 2. The contradiction is gone from the description. It now separates the input constraint, which mitigates without closing, from the two unmitigated defects, and states that the The same "two known upstream defects … neither affects a normal release" sentence sits in zenoh-kotlin's 3. Both inaccuracies fixed.
What still differs from zenoh-kotlin, and why. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
.github/workflows/release-github.yml:200
- The current shared action always uses the newest existing release as
--notes-start-tag. If this recovery is delayed until a later version has shipped, creating the older missing release produces reversed or empty generated notes. Since delayed standalone recovery is this workflow's purpose, fix and pin the upstream action or compute the recovered version's actual predecessor instead.
- uses: eclipse-zenoh/ci/publish-crates-github@main
PUBLISHING.md:549
- The linked issue tracks three defects, not two. It is also inaccurate that none affects normal releases: the shell construction is a security exposure and the unused helper installation delays every invocation; only the generated-note bounds are recovery-specific.
Two upstream defects are known and tracked in
[eclipse-zenoh/ci#470](https://github.com/eclipse-zenoh/ci/issues/470). Neither
affects a normal release, and both are left upstream deliberately — a fix there
reaches every Zenoh repository, whereas working around them here would fix one.
.github/workflows/release-github.yml:199
- The linked issue tracks three defects, not two, and the statement that none affects a normal release is inaccurate: shell command construction is a security exposure and the unused Rust-helper installation delays every invocation. Update this comment so operators do not underestimate the known impact.
# Two known upstream defects are tracked in eclipse-zenoh/ci#470; neither
# affects a normal release. They are deliberately not worked around here:
# a fix upstream reaches every Zenoh repository, one here reaches this
# repository only.
| # affects a normal release. They are deliberately not worked around here: | ||
| # a fix upstream reaches every Zenoh repository, one here reaches this | ||
| # repository only. | ||
| - uses: eclipse-zenoh/ci/publish-crates-github@main |
The problem
Nothing is broken in this repository today:
release.ymlhas apublish-githubjob, and 1.10.0 has its GitHub release. What is missing is the way back when that job does not run.The steps that create the release sit inline in
release.yml, so the only way to run them is to runrelease.yml— which also cuts the release branch, force-pushes the tag, and publishes to Maven Central. A Maven Central publication cannot be withdrawn, so a version that reaches Central without a GitHub release cannot be fixed by re-running: the version cannot be republished, and the re-run would force-move the tag onto a different commit than the artifact was built from.zenoh-kotlin reached exactly that state — 1.10.0 published to Central, no GitHub release, no way to create one — and fixed it in zenoh-kotlin#708. That pull request notes this repository needs the same treatment. This is it.
How the workflows fit together
release.ymlis the entry point, and it is the workflow behind Actions → Release. It calls the other three as jobs; nothing callsrelease.yml.publish-dokkaandpublish-githubboth wait forpublish, so neither announces a version whose upload failed.publish-dokka.ymlbuilds the API documentation with Dokka, the Kotlin documentation generator, and deploys it togh-pages, the branch GitHub serves as a website.The two workflows marked also runs alone are what this pull request is about.
Two inputs of
release.ymldecide what a run does, and both predate this pull request.live-runseparates a rehearsal from a real release.maven_publish, on by default, switches off the upload to Maven Central.What this changes
The GitHub release job moves into a workflow that also runs alone
The steps move verbatim out of
release.ymlinto the new.github/workflows/release-github.yml, whichrelease.ymlcalls as its last job. You can also start that workflow yourself from the Actions tab, which is the point: recovering a missing GitHub release must not require re-running the tag and publish jobs.The runner changes from
macos-latesttoubuntu-latest, matching zenoh-kotlin: nothing in this job needs macOS.Two checks before the release is created
versionis an input ofrelease-github.yml: a release number such as1.10.0.ci/scripts/bump-and-tag.bash, the script thetagjob runs, writes that number intoversion.txtat the repository root and passes it verbatim togit tag, so one string names both the release and its tag.eclipse-zenoh/ci/publish-crates-github— the shared action that creates the release, and the same one this repository already used — passes--verify-tagtogh release create, the GitHub CLI command for creating a release, which refuses a version with no tag of that name. Two further checks run before the action:version.txtat the tag equals theversioninputversion.txtnames a different versioncheck-mavenThe first check is narrow.
bump-and-tag.bashwritesversion.txtand creates the tag in the same run, so every tag the pipeline made passes. What that check rejects is a tag pointing at a commit whoseversion.txtnames a different version. A tag made by hand on the right commit passes, so the check does not establish who made the tag.check-maven, an input ofrelease-github.yml, is what rejects a rehearsal tag. A rehearsal is a release run under a throwaway version number — 1.10.0 was rehearsed as1.10.0-rc1— and it pushes a real tag. Rehearsal tags are self-consistent, so they pass the first check:version.txtat1.10.0-rc1reads1.10.0-rc1.check-mavenreports its two failures separately, because they call for different responses: Central says the version is not there, or Central does not answer. The release pipeline switchescheck-mavenoff, because the publish job that just uploaded the version is proof enough. A manual run has no such proof, socheck-mavendefaults to on there.The documentation deploy also runs alone now
publish-dokka.ymldeclared a manual trigger with no inputs, so a manual run received nothing:live-runwas false, the deploy step was skipped, and the build usedmain. Starting that workflow by hand built the documentation site and discarded it, while reporting success.publish-dokka.ymlnow declareslive-runandbranchfor manual runs as well.branchis required, so a manual run cannot overwrite the published documentation with the unreleased API frommain.Nothing is announced when the upload is skipped
publish.ymlgates only its upload step onmaven_publish, so the publish job succeeds whether or not anything was uploaded. Both the GitHub release and the documentation deploy are now gated onmaven_publishas well. Without that gate, a live run with uploads disabled would announce a version that was never published.release.ymlalso gainsgithub_release(default on), so a Maven-only release stays expressible.A live run with uploads disabled is rejected
maven_publishsuppresses the upload; it does not turn a run into a rehearsal. A live run withmaven_publishoff cut the real release branch and force-pushed the real tag while publishing nothing, leaving a tag for a version that exists nowhere. Thetagjob now refuses that combination before creating anything.Nothing is lost. Rehearsing without uploading is
live-runoff, which runs the same build and leaves a disposable dry-run branch.live-runmaven_publish<version>-SNAPSHOTto the mutable snapshot repositoryPUBLISHING.md
The document said the release path had never run — 1.10.0 published from it on 2026-08-17 — and said
zenoh-flat-jnihad never been released, which stopped being true withzenoh-flat-jni:1.10.0. Both corrected, and four sections added: the table above, how to create a GitHub release on its own, what a failed release leaves behind, and how to recover a step that failed after Maven Central.Recovery after Maven Central has accepted a version
At that point the release cannot be re-run, for the reason given at the top. So each remaining step recovers on its own:
publishtagpublishranpublish-githubpublish-dokkabranchset torefs/tags/<version>mainis deliberately untouched by a release —version.txtis bumped on the release branch only — so no post-release commit needs reconstructing.Nothing here needs recovering right now. 1.10.0 has its release and its documentation; this is the machinery for the next time a release stops halfway.
Two upstream defects
Filed by zenoh-kotlin#708 as eclipse-zenoh/ci#470, and this repository has been running the same action:
publish-crates-githubbuilds itsghcommand by joining strings and runs the result through a shell, with the bot token in the environment. Theversionandbranchthe action is given therefore reachsh -c. The action should use the argument-array helper it already has.gh, on every invocation. That costs every release about 40 seconds and changes nothing the action produces.Only the first defect affects safety.
release-github.ymlconstrains its own two inputs to plain ref characters, which is worth doing but is not a fix: the action also passes the newest release's tag name into that command string, and Git accepts tag names carrying shell syntax. The boundary closes upstream or not at all.None of the three is worked around here: a fix upstream reaches every Zenoh repository, a workaround here reaches one.
Testing
Not run end to end — this is release machinery, and running it live is the situation being fixed. What was checked:
check-mavenagainst a published version (zenoh-java:1.10.0), a rehearsal version (1.10.0-rc1), and an unreachable host.1.10.0,release/1.10.0and1.10.0-rc1, and rejects a value carrying shell syntax.version.txtat both existing tags is self-consistent, confirming that the first check passes a rehearsal tag and onlycheck-mavenstops it.tag → publish → {publish-dokka, publish-github}.markdownlint-cli2is clean onPUBLISHING.md, and every internal link in it resolves.Difference from zenoh-kotlin#708
The two repositories now run the same workflow, with the same guards and the same inputs. Two things differ, both because this repository never lost the job:
release-github.ymlhere says the steps moved out ofrelease.ymlrather than that they were deleted in the flat-jni transition, and there is no missing release to recover.