ci(releases): add recoverable releases, lighten dev ci load, better p… - #531
Conversation
…ropagation - Force-publish workflow_dispatch mode enables recovering from partial failures. A resolve step overrides rp outputs so "Re-run failed jobs" can finish the publish for an existing tag without re-bumping versions. - Full Python wheel matrix only on main/releases; dev builds one smoke wheel. - Propagation PRs touch a real file so release-please attributes the commit. Empty commits were likely never mapped to components, so no release triggered. - Publish ordering (GH release before PyPI, npm last) allows safe retries. - cleanup-drafts job deletes stale dry-run drafts after 7 days. - Unpinned actions (upload-artifact, download-artifact, setup-python) pinned; persist-credentials: false added on checkouts.
|
Warning Review limit reached
Next review available in: 34 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughThe release workflow adds forced component republishing, resolves effective release outputs, adjusts Python artifact builds and publishing paths, creates marker-based propagation PRs, pins actions, and deletes stale draft releases. ChangesRelease workflow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant WorkflowDispatch
participant ResolveEffectiveOutputs
participant PythonBuild
participant publish-py
participant publish-py-test
WorkflowDispatch->>ResolveEffectiveOutputs: provide forced component and tag
ResolveEffectiveOutputs->>PythonBuild: emit effective py_created, py_tag, and py_version
PythonBuild->>publish-py: upload artifacts for created Python releases
PythonBuild->>publish-py-test: upload artifacts for non-created releases
publish-py->>publish-py: publish to PyPI
publish-py-test->>publish-py-test: publish to TestPyPI and create draft release
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 8✅ Passed checks (8 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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 444-468: Make the propagation sequence idempotent in
.github/workflows/release.yml lines 444-468 and 492-513: guard marker updates
and commits so unchanged versions produce no commit failure, push branches with
--force-with-lease, and query for an existing open PR for $BRANCH before
invoking gh pr create. Apply the same behavior at both sites, preferably by
reusing a shared script or composite action if practical.
- Around line 110-116: Update the “Debug resolved outputs” step so the fallback
version is passed through the step’s env block using a dedicated variable, then
reference that environment variable in the run script instead of interpolating
steps.fallback_version.outputs.fallback_version directly. Keep the existing
debug output unchanged.
- Around line 92-101: Add validation in the forced-release branch around
FORCE_COMPONENT and FORCE_TAG to require the tag’s component prefix, using the
configured component names and tag separator (for example,
`${FORCE_COMPONENT}-v`). Reject mismatches with an error and exit before setting
created, tag, or version; preserve the existing missing-tag validation and
valid-tag parsing.
- Around line 372-403: Update the publish-py-test job to avoid attempting
TestPyPI OIDC uploads unless the required trusted publisher is configured;
otherwise make the dry run local-only by removing or bypassing the TestPyPI
publish step while retaining artifact validation. Complete the truncated comment
above publish-py-test so it accurately describes the dry-run behavior and
TestPyPI target.
- Around line 531-536: Update the draft-release filter in the gh api query to
use the release’s own draft creation timestamp rather than created_at,
preserving the existing 7-day cutoff, draft, and tag-name conditions. If no
draft-specific timestamp is available, document the known timestamp skew
explicitly instead of silently treating created_at as draft age.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: af9f60dd-dab8-42df-895b-8f8af5c4936e
📒 Files selected for processing (1)
.github/workflows/release.yml
📜 Review details
⚠️ CI failures not shown inline (2)
GitHub Actions: CI / 3_Set Docker image.txt: ci(releases): add recoverable releases, lighten dev ci load, better p…
Conclusion: failure
##[group]Run BASE_IMAGE="ghcr.io/ryan-millard/img2num-dev"
�[36;1mBASE_IMAGE="ghcr.io/ryan-millard/img2num-dev"�[0m
�[36;1m�[0m
�[36;1mMAIN_IMAGE="${BASE_IMAGE}:main"�[0m
�[36;1mDEV_IMAGE="${BASE_IMAGE}:dev"�[0m
�[36;1m�[0m
�[36;1mPR_NUMBER="531"�[0m
�[36;1m�[0m
�[36;1m# Try PR image first (highest priority)�[0m
�[36;1mif [[ -n "$PR_NUMBER" ]]; then�[0m
�[36;1m PR_TAG="pr-${PR_NUMBER}"�[0m
�[36;1m GHCR_IMAGE="ghcr.io/ryan-millard/img2num-dev:${PR_TAG}"�[0m
�[36;1m DH_IMAGE="ryan-millard/img2num-dev:${PR_TAG}"�[0m
�[36;1m�[0m
�[36;1m GHCR_IMAGE_EXISTS=$(gh api \�[0m
�[36;1m -H "Accept: application/vnd.github+json" \�[0m
�[36;1m /users/ryan-millard/packages/container/img2num-dev/versions \�[0m
�[36;1m --jq "any(.metadata.container.tags[]? == \"${PR_TAG}\")")�[0m
�[36;1m�[0m
�[36;1m DH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \�[0m
�[36;1m --connect-timeout 2 --max-time 5 \�[0m
�[36;1m "https://registry.hub.docker.com/v2/repositories/ryan-millard/img2num-dev/tags/${PR_TAG}/")�[0m
�[36;1melse�[0m
�[36;1m GHCR_IMAGE_EXISTS="false"�[0m
�[36;1m DH_STATUS="404"�[0m
�[36;1mfi�[0m
�[36;1m�[0m
�[36;1mif [[ "$GHCR_IMAGE_EXISTS" == "true" ]]; then�[0m
�[36;1m echo "image=${GHCR_IMAGE}" >> $GITHUB_OUTPUT�[0m
�[36;1m echo "image=${GHCR_IMAGE}"�[0m
�[36;1melif [[ "$DH_STATUS" == "200" ]]; then�[0m
�[36;1m echo "image=${DH_IMAGE}" >> $GITHUB_OUTPUT�[0m
�[36;1m echo "image=${DH_IMAGE}"�[0m
�[36;1melse�[0m
�[36;1m # Fallback: main/dev (never PR ref names)�[0m
�[36;1m BRANCH="531/merge"�[0m
�[36;1m�[0m
�[36;1m case "$BRANCH" in�[0m
�[36;1m main)�[0m
�[36;1m IMAGE="$MAIN_IMAGE"�[0m
�[36;1m ;;�[0m
�[36;1m dev|*)�[0m
�[36;1m IMAGE="$DEV_IMAGE"�[0m
�[36;1m ;;�[0m
�[36;1m esac�[0m
�[36;1m�[0m
�[36;1m echo "image=${IMAGE}" >> $GITHUB_OUTPUT�[0m
�[36;1m echo "Using fallback image: ${IMAGE}"�[0m
�[36;1mfi�[0m
shell: /usr/bin/bash -e {0}
env:
GH_***REDACTED***
##[endgroup]
...
GitHub Actions: CI / Set Docker image: ci(releases): add recoverable releases, lighten dev ci load, better p…
Conclusion: failure
##[group]Run BASE_IMAGE="ghcr.io/ryan-millard/img2num-dev"
�[36;1mBASE_IMAGE="ghcr.io/ryan-millard/img2num-dev"�[0m
�[36;1m�[0m
�[36;1mMAIN_IMAGE="${BASE_IMAGE}:main"�[0m
�[36;1mDEV_IMAGE="${BASE_IMAGE}:dev"�[0m
�[36;1m�[0m
�[36;1mPR_NUMBER="531"�[0m
�[36;1m�[0m
�[36;1m# Try PR image first (highest priority)�[0m
�[36;1mif [[ -n "$PR_NUMBER" ]]; then�[0m
�[36;1m PR_TAG="pr-${PR_NUMBER}"�[0m
�[36;1m GHCR_IMAGE="ghcr.io/ryan-millard/img2num-dev:${PR_TAG}"�[0m
�[36;1m DH_IMAGE="ryan-millard/img2num-dev:${PR_TAG}"�[0m
�[36;1m�[0m
�[36;1m GHCR_IMAGE_EXISTS=$(gh api \�[0m
�[36;1m -H "Accept: application/vnd.github+json" \�[0m
�[36;1m /users/ryan-millard/packages/container/img2num-dev/versions \�[0m
�[36;1m --jq "any(.metadata.container.tags[]? == \"${PR_TAG}\")")�[0m
�[36;1m�[0m
�[36;1m DH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \�[0m
�[36;1m --connect-timeout 2 --max-time 5 \�[0m
�[36;1m "https://registry.hub.docker.com/v2/repositories/ryan-millard/img2num-dev/tags/${PR_TAG}/")�[0m
�[36;1melse�[0m
�[36;1m GHCR_IMAGE_EXISTS="false"�[0m
�[36;1m DH_STATUS="404"�[0m
�[36;1mfi�[0m
�[36;1m�[0m
�[36;1mif [[ "$GHCR_IMAGE_EXISTS" == "true" ]]; then�[0m
�[36;1m echo "image=${GHCR_IMAGE}" >> $GITHUB_OUTPUT�[0m
�[36;1m echo "image=${GHCR_IMAGE}"�[0m
�[36;1melif [[ "$DH_STATUS" == "200" ]]; then�[0m
�[36;1m echo "image=${DH_IMAGE}" >> $GITHUB_OUTPUT�[0m
�[36;1m echo "image=${DH_IMAGE}"�[0m
�[36;1melse�[0m
�[36;1m # Fallback: main/dev (never PR ref names)�[0m
�[36;1m BRANCH="531/merge"�[0m
�[36;1m�[0m
�[36;1m case "$BRANCH" in�[0m
�[36;1m main)�[0m
�[36;1m IMAGE="$MAIN_IMAGE"�[0m
�[36;1m ;;�[0m
�[36;1m dev|*)�[0m
�[36;1m IMAGE="$DEV_IMAGE"�[0m
�[36;1m ;;�[0m
�[36;1m esac�[0m
�[36;1m�[0m
�[36;1m echo "image=${IMAGE}" >> $GITHUB_OUTPUT�[0m
�[36;1m echo "Using fallback image: ${IMAGE}"�[0m
�[36;1mfi�[0m
shell: /usr/bin/bash -e {0}
env:
GH_***REDACTED***
##[endgroup]
...
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**
⚙️ CodeRabbit configuration file
.github/workflows/**: GitHub Actions workflows. Review for:
- SHA-pinned action versions for third-party actions (security best practice).
- Secrets accessed only via ${{ secrets.* }} — never hardcoded.
- Least-privilege permissions on each job/workflow.
- Correct job dependency ordering (needs:) and if/condition logic.
Files:
.github/workflows/release.yml
🧠 Learnings (2)
📚 Learning: 2026-05-01T22:50:11.527Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 339
File: release-please-config.json:18-47
Timestamp: 2026-05-01T22:50:11.527Z
Learning: In this repo, release-please-action v4 preserves '/' verbatim in slash-containing path-based package keys when emitting GitHub Actions output names (e.g., `bindings/c--release_created`). When referencing these step outputs in `job.outputs` (and other expressions), use bracket notation with the exact output name: `${{ steps.release.outputs['bindings/c--release_created'] }}` rather than dot notation. If needed, map the complex step output to a clean job-level output alias so downstream jobs can use dot notation via that alias.
Applied to files:
.github/workflows/release.yml
📚 Learning: 2026-05-19T17:30:09.565Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 375
File: .github/workflows/cmake-build.yml:86-88
Timestamp: 2026-05-19T17:30:09.565Z
Learning: In Ryan-Millard/Img2Num CI/workflow YAMLs, any `uv sync` command used for the Python package build must include `--no-build-isolation` (do not remove it). If you need deterministic dependency installs for CI, you may add `--frozen` alongside it (e.g., `uv sync --frozen --no-build-isolation`), and it should not conflict with the repo’s build setup.
Applied to files:
.github/workflows/release.yml
🪛 zizmor (1.26.1)
.github/workflows/release.yml
[info] 116-116: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 344-344: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
[warning] 378-378: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
[info] 339-339: workflow or action definition without a name (anonymous-definition): this job
(anonymous-definition)
[info] 373-373: workflow or action definition without a name (anonymous-definition): this job
(anonymous-definition)
[info] 363-363: action functionality is already included by the runner (superfluous-actions): use gh release in a script step
(superfluous-actions)
[info] 393-393: action functionality is already included by the runner (superfluous-actions): use gh release in a script step
(superfluous-actions)
[warning] 523-523: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
[info] 518-518: workflow or action definition without a name (anonymous-definition): this job
(anonymous-definition)
🔇 Additional comments (6)
.github/workflows/release.yml (6)
7-16: LGTM!
29-53: LGTM!
196-196: LGTM!Also applies to: 215-215, 262-265
279-309: LGTM!
322-336: LGTM!
338-370: LGTM!
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/workflows/release.yml (2)
103-117: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winReject malformed forced tags before writing job outputs.
The current prefix check also accepts strings like
packages-py-v\njs_created=evil\njs_tag=x, and those newlines are written directly to$GITHUB_OUTPUT, creating extra release outputs and causing downstream release/tag/version values to come from the injected output lines. Reject CR/LF and use the exact component version pattern before emittingcreated,tag, andversion.🤖 Prompt for 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. In @.github/workflows/release.yml around lines 103 - 117, Validate FORCE_TAG before assigning tag/version or writing outputs: reject any CR/LF characters and require the exact expected component tag/version pattern, not merely the expected prefix. Keep the existing mismatch error path, and only emit the created, tag, and version outputs after the forced tag passes both validations.
369-381: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftMake forced Python publishing retry-safe.
Recovery mode can set
py_created=truewith the forced tag, so the production Python publish job also re-runs withoutskip-existing. If a previous run already uploaded any distribution file, retrying will fail due to the naming collision and recovery cannot use “Re-run failed jobs” for this job. Addskip-existingonly for forced recovery, or implement per-file reconciliation while keeping normal releases fail-loud.🤖 Prompt for 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. In @.github/workflows/release.yml around lines 369 - 381, Update the production Python publish step using pypa/gh-action-pypi-publish so forced recovery runs are retry-safe by enabling skip-existing only when the workflow indicates the forced recovery mode. Keep skip-existing disabled for normal releases so duplicate uploads still fail loudly, and preserve the existing package directory and release asset behavior.
🤖 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.
Outside diff comments:
In @.github/workflows/release.yml:
- Around line 103-117: Validate FORCE_TAG before assigning tag/version or
writing outputs: reject any CR/LF characters and require the exact expected
component tag/version pattern, not merely the expected prefix. Keep the existing
mismatch error path, and only emit the created, tag, and version outputs after
the forced tag passes both validations.
- Around line 369-381: Update the production Python publish step using
pypa/gh-action-pypi-publish so forced recovery runs are retry-safe by enabling
skip-existing only when the workflow indicates the forced recovery mode. Keep
skip-existing disabled for normal releases so duplicate uploads still fail
loudly, and preserve the existing package directory and release asset behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 285d3a97-c591-417f-a213-94aac48780e8
📒 Files selected for processing (1)
.github/workflows/release.yml
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
- GitHub Check: Build C/C++ / Build WASM (bindings/js)
- GitHub Check: Build C/C++ / Build C & C++
- GitHub Check: Build C/C++ / Build Python
- GitHub Check: Lint & Validate Code
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**
⚙️ CodeRabbit configuration file
.github/workflows/**: GitHub Actions workflows. Review for:
- SHA-pinned action versions for third-party actions (security best practice).
- Secrets accessed only via ${{ secrets.* }} — never hardcoded.
- Least-privilege permissions on each job/workflow.
- Correct job dependency ordering (needs:) and if/condition logic.
Files:
.github/workflows/release.yml
🧠 Learnings (2)
📚 Learning: 2026-05-01T22:50:11.527Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 339
File: release-please-config.json:18-47
Timestamp: 2026-05-01T22:50:11.527Z
Learning: In this repo, release-please-action v4 preserves '/' verbatim in slash-containing path-based package keys when emitting GitHub Actions output names (e.g., `bindings/c--release_created`). When referencing these step outputs in `job.outputs` (and other expressions), use bracket notation with the exact output name: `${{ steps.release.outputs['bindings/c--release_created'] }}` rather than dot notation. If needed, map the complex step output to a clean job-level output alias so downstream jobs can use dot notation via that alias.
Applied to files:
.github/workflows/release.yml
📚 Learning: 2026-05-19T17:30:09.565Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 375
File: .github/workflows/cmake-build.yml:86-88
Timestamp: 2026-05-19T17:30:09.565Z
Learning: In Ryan-Millard/Img2Num CI/workflow YAMLs, any `uv sync` command used for the Python package build must include `--no-build-isolation` (do not remove it). If you need deterministic dependency installs for CI, you may add `--frozen` alongside it (e.g., `uv sync --frozen --no-build-isolation`), and it should not conflict with the repo’s build setup.
Applied to files:
.github/workflows/release.yml
🔇 Additional comments (4)
.github/workflows/release.yml (4)
126-126: Duplicate: keepfallback_versionout of shell interpolation.Line 126 still expands a value derived from
GITHUB_REF_NAMEdirectly into therunscript. Pass it throughenvand echo the environment variable instead; the prior injection finding remains unresolved.
382-413: Duplicate: verify TestPyPI trusted publishing configuration.This path still requests OIDC publishing and uploads to TestPyPI. Confirm the repository/workflow is configured as a TestPyPI trusted publisher; otherwise non-release runs will fail after creating the draft release.
454-478: Duplicate: propagation remains non-idempotent.Retries can still fail on an unchanged marker commit or an already-existing pull request. Guard no-op commits and existing PRs before pushing or invoking
gh pr create.Also applies to: 502-523
7-16: LGTM!Also applies to: 23-54, 61-102, 120-125, 299-316, 343-368
d8001b5 to
9e309ef
Compare
@coderabbitai I don't have time to update the body of this PR. Please will you do it for me.
Force-publish workflow_dispatch mode enables recovering from partial failures. A resolve step overrides rp outputs so "Re-run failed jobs" can finish the publish for an existing tag without re-bumping versions.
Full Python wheel matrix only on main/releases; dev builds one smoke wheel.
Propagation PRs touch a real file so release-please attributes the commit. Empty commits were likely never mapped to components, so no release triggered.
Publish ordering (GH release before PyPI, npm last) allows safe retries.
cleanup-drafts job deletes stale dry-run drafts after 7 days.
Unpinned actions (upload-artifact, download-artifact, setup-python) pinned; persist-credentials: false added on checkouts.