| name | merge-renovate-prs |
| description | Re-trigger failed CI on Renovate dependency PRs, watch the runs to completion, auto-merge the green ones, and report the failures. Use after a Renovate batch when many renovate/* PRs failed CI from rate limiting or flaky external dependants, or when the user asks to retry, unblock, or merge renovate PRs. |
| disable-model-invocation | true |
| allowed-tools | Bash(gh *), Bash(git *), Read, Write |
Merge Renovate PRs
Renovate opens many renovate/* PRs at once. Their CIs run simultaneously and frequently fail from rate limiting by external dependants, not real regressions. This skill retries the failed CI jobs on a batch of Renovate PRs, waits for them to finish, squash-merges the green ones, and reports the rest.
Repository: algolia/api-clients-automation. The CI workflow is Checks (.github/workflows/check.yml); the gate job is check_green. A separate workflow validates the PR title (pr-title.yml).
Defaults (decided — do not re-ask)
- Selection: the oldest 5 open PRs whose head branch starts with
renovate/ (by createdAt, ascending).
- Rerun scope: only failed jobs (
gh run rerun --failed).
- Merge method: squash (
gh pr merge --squash).
If the user passed explicit PR numbers, act on those instead of selecting.
Workflow
Copy this checklist and track progress:
- [ ] 1. Preflight (gh auth, repo)
- [ ] 2. Select the 5 PRs
- [ ] 3. Re-trigger failed jobs on all of them
- [ ] 4. Watch every PR's CI to completion
- [ ] 5. Squash-merge the green PRs
- [ ] 6. Report the failures
1. Preflight
Confirm the GitHub CLI is authenticated and resolve the repo:
gh auth status
gh repo view --json nameWithOwner -q .nameWithOwner
If gh is not authenticated, stop and tell the user to run gh auth login.
2. Select the 5 PRs
gh pr list --state open --limit 200 \
--json number,title,headRefName,createdAt,url \
--jq '[.[] | select(.headRefName | startswith("renovate/"))] | sort_by(.createdAt) | .[:5]'
If fewer than 5 match, act on all that match. If none match, stop and report "No open renovate/* PRs found."
Print the selected PRs (number, title, branch) before acting.
3. Re-trigger failed jobs
Do this for all selected PRs first (so their reruns proceed in parallel), then move to watching.
For each PR, find the latest Checks run on its head branch and rerun only the failed jobs:
RUN_ID=$(gh run list --branch "<headRefName>" --workflow check.yml \
--limit 1 --json databaseId -q '.[0].databaseId')
gh run rerun "$RUN_ID" --failed
Notes:
--failed reruns only the failed jobs from that run, minimizing CI load (the whole point — avoid re-hammering rate-limited dependants).
- If the latest run has no failed jobs (already passing or still running), skip the rerun for that PR and note it.
- If
gh run rerun reports the run is too old to rerun, push an empty commit to retrigger only as a last resort, and note it: gh pr comment is not a retrigger. Prefer asking before pushing commits to a Renovate branch.
- The PR-title check (
pr-title.yml) is a separate workflow; it rarely fails for Renovate. If it failed, surface it in the report rather than rerunning blindly.
4. Watch CI to completion
Watch each selected PR until all its checks finish. Run the watches and let them complete (they can run concurrently across PRs):
gh pr checks <number> --watch --fail-fast=false
--watch blocks until every check reaches a terminal state. Do not poll manually in a tight loop.
5. Determine green and merge
After watching, read the final check states per PR:
gh pr checks <number> --json name,state,bucket
Classify the PR:
- GREEN: every check's
bucket is pass or skipping, and check_green is pass.
- FAILED: any check's
bucket is fail (FAILURE, CANCELLED, TIMED_OUT, ERROR).
- PENDING: anything still
pending after the watch (treat as FAILED for the report, noting it timed out).
Expected skips (not failures): push_and_release (always skips on PRs), notification (forks), language client * jobs for unaffected languages.
For each GREEN PR, squash-merge:
gh pr merge <number> --squash --delete-branch
If the merge is blocked (e.g. required approvals, out-of-date branch), do not force it. Record the block reason in the report and move on. Only use --admin or update-branch if the user explicitly asked to bypass protections.
6. Report
Always end with a single table summarizing every PR acted on.
## Renovate PR batch — <N> PRs
| PR | Branch | Action | Result |
|----|--------|--------|--------|
| [#1234](url) | renovate/foo | reran 3 failed jobs | ✅ merged (squash) |
| [#1235](url) | renovate/bar | reran 1 failed job | ❌ failed |
| [#1236](url) | renovate/baz | already green | ✅ merged (squash) |
| [#1237](url) | renovate/qux | reran 2 failed jobs | ⏳ still pending (watch timed out) |
| [#1238](url) | renovate/quux | reran 1 failed job | 🚫 merge blocked (needs approval) |
### Failures
For each ❌/⏳ PR, list the failing check(s) and the reason:
- **#1235** `client go@1.24` — <1–3 line summary from the failed log>
- **#1237** `client swift@6 linux` — still running at watch timeout
For each failed check, pull a short reason from the failed logs:
gh run view <RUN_ID> --log-failed
Summarize each failure in 1–3 lines. If a failure looks like rate limiting / a transient external dependant (timeouts, 429s, network resets) rather than a code regression, say so and suggest re-running the skill once limits reset.
Rebasing a stale renovate PR onto the base branch
When a PR's mergeStateStatus is DIRTY (conflicts) or it's simply behind chore/renovateBaseBranch, it needs a rebase before it can go green/merge. Do NOT naively git rebase origin/chore/renovateBaseBranch and replay every commit on the branch.
Why: Renovate periodically recreates chore/renovateBaseBranch from main, so the base already contains newer-or-equal versions of most bumps the PR branch still carries. The PR branch's older bump commits are stale/superseded (e.g. the base has @types/node v26.0.1 while the PR still carries a commit setting it to v26). Replaying them all would try to downgrade those deps and manufacture needless yarn.lock/package.json conflicts.
Correct approach — replay only the PR's own unique commits with git rebase --onto (this mirrors what Renovate's own "rebase" checkbox does), in a detached throwaway worktree so the current branch/working tree is untouched:
git fetch origin chore/renovateBaseBranch <pr-branch>
git log --oneline origin/chore/renovateBaseBranch..origin/<pr-branch>
Identify the commits genuinely unique to this PR — its own dependency bump plus any follow-up (e.g. a "format" commit). Everything below the first unique commit is stale and already on the base in newer form, so drop it. Then:
WT=/tmp/renovate-rebase-<pr>
git worktree add --detach "$WT" origin/<pr-branch>
cd "$WT"
git rebase --onto origin/chore/renovateBaseBranch <first-unique-commit>^
- If the replay is clean (usual case), verify
git diff --stat origin/chore/renovateBaseBranch..HEAD shows only the PR's own changes, and that package.json and yarn.lock reference the same bumped version.
- If a
yarn.lock/package.json conflict appears, resolve per the lockfile rule: take the base's lockfile, keep the PR's version bump in package.json, then regenerate the lock (never hand-edit it).
Then force-push with lease and clean up:
git push --force-with-lease=<pr-branch>:origin/<pr-branch> origin HEAD:<pr-branch>
cd - && git worktree remove --force "$WT"
Merge state should flip DIRTY → UNSTABLE (conflict resolved, CI running) and a fresh Checks run triggers on the new head. This rebase (rewriting a renovate branch's history + force-push) is authorized only when the user explicitly asks to rebase — it's the one exception to "never push to a renovate branch."
Gotcha: never git worktree remove the directory you're currently cd'd into — deleting the shell's cwd wedges the session. cd out first.
Notes
- Don't block on the base branch CI or the rebase "storm." Each merge into
chore/renovateBaseBranch advances it, which makes Renovate auto-rebase every other open renovate/* PR (cancelling their in-flight runs and starting fresh ones) and triggers a run on the base-branch→main PR. Do not wait for those base-branch runs or for the whole storm to drain. Stay focused on the target PRs: keep retrying their own check.yml runs and merge each the moment its own checks are green, regardless of what the base branch or other PRs are doing.
- Merges advancing the base will cancel/supersede reruns you started on the other targets — that's expected. Just re-check each target, rerun its failed/cancelled jobs, and merge when green; iterate until the batch is done rather than serializing behind base-branch CI.
- Renovate PRs in this repo target the
chore/renovateBaseBranch base, not main — gh pr merge targets each PR's own base automatically, so no special handling is needed.
- Keep CI load low: rerun only failed jobs, never the whole workflow, and never push commits to retrigger unless the user approves.
- Never edit generated client code to "fix" a Renovate failure — these PRs only bump dependencies; a real failure should be surfaced, not patched.
- Never merge a Python runtime bump to 3.14+. The PyPI release toolchain (poetry/twine) is not compatible with Python 3.14+, and the release workflow fails to publish the
algoliasearch package. The python-version in clients/algoliasearch-client-python/.github/workflows/release.yml must stay on the latest 3.13.x patch. Before merging any PR (including grouped/batch ones), check its diff for this file: if it bumps python-version to 3.14 or above, do not merge — revert to the latest 3.13.x patch or close the PR with an explanation.
- Hold bumps that would force a customer-facing runtime bump. If merging a bump requires raising a published client's minimum language/runtime, it's a breaking change for consumers — close the PR with a short explanation instead of migrating (same policy as JUnit 6 → JVM 17+, pytest-aiohttp → Py 3.10+). Known case:
renovate/melos-8.x (Dart) — melos ≥7 drops melos.yaml for Dart pub workspaces, which requires resolution: workspace on every package and raises the minimum Dart SDK to 3.5+ on all published packages. Keep the Dart client on melos 6.x. (Renovate may recreate a closed PR on its next run; to hold permanently, add the dep to the Renovate config's ignore list.)