ci(release): keep image release on its own v* track - #1004
Conversation
The release.yml resolver picked the newest release repo-wide via
`gh release list --limit 1`, with no prefix filter. When the separate
standalone-dmr track published `dmr-v0.1.0` (newest release overall),
the container-image release grabbed it and ran it through the v*
auto-bump logic. `${LATEST_TAG#v}` only strips a leading `v`, so
`dmr-v0.1.0` was mangled into the invalid tag `vdmr-v0.1.1`, which was
then baked into the server image, pushed as a git tag, and propagated
downstream — breaking verify-docker-ce (server `vdmr-v0.1.1` vs CE
client `v1.2.5`).
Restrict the resolver to this workflow's own strict-semver (vX.Y.Z)
scheme:
- filter the GitHub release lookup to `^v[0-9]+\.[0-9]+\.[0-9]+$`
- filter the git-tag fallback the same way (the `v*` glob also matched
stray tags like `vdmr-v0.1.1`)
- add a post-resolve guard that aborts prepare if RELEASE_TAG is not
valid vX.Y.Z, so a malformed tag can never reach the image build or
downstream release triggers
The standalone `dmr` binary continues to release independently via
release-dmr.yml on `dmr-v*` tags.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported. |
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path=".github/workflows/release.yml" line_range="72-73" />
<code_context>
+ # is released separately via release-dmr.yml on `dmr-v*` tags. Filter
+ # to our own scheme so we never pick up the other track's release
+ # (gh release list is newest-first by creation, so `first` = newest).
+ LATEST_RELEASE_TAG=$(gh release list --limit 100 --json tagName \
+ --jq 'map(.tagName) | map(select(test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))) | first // ""' \
2>/dev/null || echo "")
</code_context>
<issue_to_address>
**issue:** Potentially missing the latest semver release if there are many non-matching tags in the newest 100 releases
Because `gh release list` is limited to the newest 100 releases and you filter semver tags client-side, you only ever pick the newest `vX.Y.Z` tag within that 100-release window. If many non-`vX.Y.Z` tags (like `dmr-v*`) are created, the actual latest semver tag may fall outside that window and be skipped. If this is realistic for this repo, consider a larger limit or a different approach (e.g. filtering by tag pattern via `gh api`, or falling back to `git tag` when no semver match is found) so you don’t miss the true latest semver release.
</issue_to_address>
### Comment 2
<location path=".github/workflows/release.yml" line_range="138-139" />
<code_context>
+ # Guard: never let a malformed tag reach the image build or the
+ # downstream release triggers. Must be strict semver (vX.Y.Z).
+ if ! echo "$RELEASE_TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
+ echo "::error::Resolved release tag '$RELEASE_TAG' is not valid vX.Y.Z. Aborting."
+ exit 1
+ fi
</code_context>
<issue_to_address>
**suggestion:** Regex for tag validation is duplicated and could be centralized
This strict semver pattern (`^v[0-9]+\.[0-9]+\.[0-9]+$`) is now repeated (here and in release discovery). To avoid inconsistencies if the rule changes (e.g., allowing build metadata), centralize it in a shared variable or helper so all validations use the same definition.
Suggested implementation:
```
RELEASE_TAG_PATTERN='^v[0-9]+\.[0-9]+\.[0-9]+$'
LATEST_TAG=$(git tag --list 'v*' --sort=-v:refname \
| grep -E "$RELEASE_TAG_PATTERN" | head -1)
echo "No GitHub Releases found, falling back to latest git tag: ${LATEST_TAG:-<none>}"
fi
```
```
# Guard: never let a malformed tag reach the image build or the
# downstream release triggers. Must be strict semver (vX.Y.Z).
if ! echo "$RELEASE_TAG" | grep -qE "$RELEASE_TAG_PATTERN"; then
echo "::error::Resolved release tag '$RELEASE_TAG' is not valid vX.Y.Z. Aborting."
exit 1
fi
```
- Ensure `RELEASE_TAG_PATTERN` is defined once within the same `run:` script block that uses both `LATEST_TAG` discovery and `RELEASE_TAG` validation. If those snippets are in different steps, extract the pattern definition into each relevant step or into a shared reusable workflow/action.
- Search the rest of `.github/workflows/release.yml` (and any related workflows) for other hard-coded uses of `'^v[0-9]+\.[0-9]+\.[0-9]+$'` and replace them with `$RELEASE_TAG_PATTERN` to keep all validations consistent.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Address PR review feedback: - Centralize the strict-semver pattern in a single RELEASE_TAG_PATTERN variable, reused by the release lookup, git-tag fallback, explicit-tag validation, and the final guard, so the rule can't drift between them. - Make the git-tag fallback trigger on "no semver match found" (not just "no releases exist"), covering the case where the newest 100 releases are all off-track (e.g. a burst of dmr-v*) and the true latest vX.Y.Z release falls outside the release-list window. git tag is unbounded, so it always finds the real latest semver tag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The
gh release list --limit 100window introduces a subtle edge case where older but still relevantvX.Y.Zreleases could be missed; consider either paginating through all releases or using--limitwith a clearer justification or comment about why 100 is sufficient. - Swallowing all
gh release liststderr output with2>/dev/nullmakes diagnosing future resolver issues harder; it may be safer to allow errors to surface or explicitly handle known failure modes with targeted messaging.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `gh release list --limit 100` window introduces a subtle edge case where older but still relevant `vX.Y.Z` releases could be missed; consider either paginating through all releases or using `--limit` with a clearer justification or comment about why 100 is sufficient.
- Swallowing all `gh release list` stderr output with `2>/dev/null` makes diagnosing future resolver issues harder; it may be safer to allow errors to surface or explicitly handle known failure modes with targeted messaging.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Address second-pass review feedback:
- Stop swallowing `gh release list` stderr with `2>/dev/null`. Capture it
and, on failure, emit a `::warning::` before falling back to git tags,
so a broken release lookup is visible in the logs instead of silently
degrading.
- Document why `--limit 100` is sufficient (the unbounded git-tag
fallback backstops the window).
- Fix a pipefail hazard the previous commit introduced: under the default
`bash -eo pipefail` shell, a no-match `grep` in the git-tag fallback
exits non-zero and would abort the step (breaking the legitimate
"fresh repo, no tags -> start at v0.1.0" path). Wrap it in
`{ grep ... || true; }`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @sourcery-ai — both high-level points addressed in 1d1d870: 1. 2. Swallowing stderr. Good call. Also fixed a related pipefail hazard while here: under the default |
Problem
Release run 28956315525 failed at
verify-docker-ce:The malformed version
vdmr-v0.1.1is the real bug — the client (v1.2.5, the Docker CE plugin) was fine.Root cause
There are two independent release tracks:
docker-model-pluginrelease.ymlv*v1.2.xdmrbinaryrelease-dmr.ymldmr-v*dmr-v0.1.xrelease.yml's resolver found "latest release" withgh release list --limit 1— no prefix filter — so it grabbed the newest release repo-wide, which wasdmr-v0.1.0from the other track. Thev*auto-bump then mangled it:${LATEST_TAG#v}strips only a leadingv, sodmr-v0.1.0→vdmr-v0.1.1. That corrupted tag was baked into the server image, pushed as a git tag, and propagated downstream.Fix
Restrict the resolver to this workflow's own strict-semver (
vX.Y.Z) scheme:^v[0-9]+\.[0-9]+\.[0-9]+$.v*glob also matched stray tags likevdmr-v0.1.1).prepareifRELEASE_TAGisn't validvX.Y.Z, so a malformed tag can never again reach the image build or downstream triggers.The standalone
dmrbinary continues to release independently viarelease-dmr.yml.Verification
v1.2.5(ignoringdmr-v0.1.0/vdmr-v0.1.1), bumps tov1.2.6, guard aborts onvdmr-v0.1.1, explicitdmr-tags rejected, normal minor/major bumps unaffected.v1.2.5(old logic returneddmr-v0.1.0).Follow-up (not in this PR)
vdmr-v0.1.1git tag + the 7vdmr-v0.1.1*Docker Hub tags; check the downstreamdocker/inference-engine-llama.cpp,docker/packaging,docker/release-repofor strayvdmr-v0.1.1artifacts.v1.2.6, restoreslatest*).🤖 Generated with Claude Code