Skip to content

fix: retry manifest push on GHCR rate limit, cap merge concurrency#449

Merged
fzipi merged 1 commit into
mainfrom
fix/merge-job-ghcr-rate-limit
Jul 5, 2026
Merged

fix: retry manifest push on GHCR rate limit, cap merge concurrency#449
fzipi merged 1 commit into
mainfrom
fix/merge-job-ghcr-rate-limit

Conversation

@fzipi

@fzipi fzipi commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

Fixes the failure in release/20260705's second run, after #448 fixed the earlier bake --set parse error.

All 36 build jobs succeeded this time, but all 12 merge jobs failed identically at docker buildx imagetools create:

ERROR: publish sha256:... to ghcr.io/coreruleset/modsecurity-crs:...: failed commit on ref "index-sha256:...": unexpected status from PUT request to https://ghcr.io/v2/coreruleset/modsecurity-crs/manifests/...: 429 Too Many Requests
toomanyrequests: retry-after: 12.5ms, allowed: 2000/minute

All 12 merge jobs (one per target) fire simultaneously and collectively burst past GHCR's short-window rate limit on manifest pushes. The retry-after values GHCR reported were all under 25ms, meaning this is a brief burst limit, not sustained throttling — a short wait should clear it.

Fix

  • strategy.max-parallel: 4 on the merge job, so at most 4 targets push manifests concurrently instead of all 12 at once
  • Retry docker buildx imagetools create up to 5 times with linear backoff (5s, 10s, 15s, 20s) on failure

Validation

  • Verified the retry loop logic in isolation (a fake command failing twice then succeeding is correctly retried and returns success)
  • actionlint and zizmor pass clean

Test plan

  • Merge and trigger a real release; confirm all 12 merge jobs succeed without hitting GHCR rate limits

AI Disclosure

Per the project's AI-Assisted Contribution Policy:

  1. AI tools used: Claude (Sonnet 5, via Claude Code)
  2. What was generated or assisted: Root-cause analysis of the release/20260705 second-run failure from the linked Actions run log (confirmed identical 429 across all 12 merge jobs before diagnosing), and the fix (retry loop with backoff plus max-parallel cap).
  3. Review performed: Verified the retry loop's control flow in an isolated shell test (fake failing command, confirmed correct retry/exit behavior) before adding it to the workflow. actionlint and zizmor both pass clean.

Summary by CodeRabbit

  • Chores
    • Improved the reliability of release publishing by reducing parallel manifest updates.
    • Added automatic retries when creating multi-platform image manifests, with short waits between attempts to handle temporary failures more gracefully.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 51 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a053375-50ca-4c50-b388-51b631cd4c32

📥 Commits

Reviewing files that changed from the base of the PR and between e5c9b8e and 3a0ef72.

📒 Files selected for processing (1)
  • .github/workflows/publish.yml
📝 Walkthrough

Walkthrough

The publish workflow's merge job now caps concurrent matrix execution at 4 and wraps the docker buildx imagetools create manifest creation command in a retry loop, attempting up to 5 times with increasing backoff delays before failing the job.

Changes

Publish Workflow Reliability

Layer / File(s) Summary
Concurrency and retry for manifest merge
.github/workflows/publish.yml
Adds max-parallel: 4 to the merge job strategy and replaces a single docker buildx imagetools create call with a retry loop (up to 5 attempts, backoff attempt * 5 seconds) that fails the job if all attempts are exhausted.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: theseion

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main workflow change: retrying manifest pushes and limiting merge concurrency to handle GHCR rate limits.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/merge-job-ghcr-rate-limit

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.

❤️ Share

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.

🧹 Nitpick comments (1)
.github/workflows/publish.yml (1)

156-162: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider disabling fail-fast alongside max-parallel.

With default fail-fast: true, one target exhausting all 5 retries (e.g. due to sustained, not just burst, throttling) will cancel in-flight/queued merge jobs for other targets — undermining the reliability goal of this change. Consider adding fail-fast: false so unrelated targets still complete independently.

♻️ Suggested addition
     strategy:
       # cap concurrent merge jobs so they don't collectively burst past GHCR's
       # manifest-push rate limit (all 12 targets pushing at once has triggered
       # 429 Too Many Requests on ghcr.io)
       max-parallel: 4
+      fail-fast: false
       matrix:
         target: ${{ fromJson(needs.prepare.outputs.targets) }}
🤖 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/publish.yml around lines 156 - 162, The publish job’s
matrix strategy currently limits concurrency with max-parallel but still uses
the default fail-fast behavior, which can cancel unrelated target jobs if one
target exhausts retries. Update the strategy in the workflow job that defines
the target matrix to explicitly set fail-fast to false alongside max-parallel so
each target’s merge job can complete independently even when another target
fails.
🤖 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.

Nitpick comments:
In @.github/workflows/publish.yml:
- Around line 156-162: The publish job’s matrix strategy currently limits
concurrency with max-parallel but still uses the default fail-fast behavior,
which can cancel unrelated target jobs if one target exhausts retries. Update
the strategy in the workflow job that defines the target matrix to explicitly
set fail-fast to false alongside max-parallel so each target’s merge job can
complete independently even when another target fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cca4238e-83d5-47fc-affe-abb513cf6c3b

📥 Commits

Reviewing files that changed from the base of the PR and between b63f25d and e5c9b8e.

📒 Files selected for processing (1)
  • .github/workflows/publish.yml

release/20260705's second attempt got past the build stage but every
one of the 12 merge jobs failed identically:

  429 Too Many Requests
  toomanyrequests: retry-after: ~10-25ms, allowed: 2000/minute

All 12 merge jobs fire at once (one per target) and collectively
burst past GHCR's short-window rate limit on manifest pushes. Caps
merge job concurrency to 4 and retries `docker buildx imagetools
create` up to 5 times with linear backoff, since the reported
retry-after windows are on the order of milliseconds and a short
wait is enough to clear the burst.
@fzipi fzipi force-pushed the fix/merge-job-ghcr-rate-limit branch from e5c9b8e to 3a0ef72 Compare July 5, 2026 21:39
@fzipi fzipi merged commit e63a7d0 into main Jul 5, 2026
33 of 38 checks passed
@fzipi fzipi deleted the fix/merge-job-ghcr-rate-limit branch July 5, 2026 21:47
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