From 213f2e8c72afb4499fce55c51c00650bf2481986 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 12:51:43 +0000 Subject: [PATCH 1/3] ci: nudge CodeRabbit awake when it rate-limits on our own PRs CodeRabbit posts a "Review limit reached" comment and only retries on its own cooldown or an explicit "@coderabbitai review" mention. If nobody pushes a new commit to a stalled PR meanwhile, that comment just sits there. Add a scheduled workflow (every ~15 min, offset from the hour boundary) that checks our own open PRs (opened by the repo owner, or carrying a "Co-Authored-By: Claude" commit trailer): if the last CodeRabbit comment is a rate-limit notice and we haven't already nudged since, post "@coderabbitai review" to wake it up. Replaces the equivalent Claude Code Remote Routine with an in-repo CI job so it doesn't depend on any particular session being alive. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01D6Naf8CcezjCbikueKdeYv --- .github/workflows/coderabbit-nudge.yml | 104 +++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/coderabbit-nudge.yml diff --git a/.github/workflows/coderabbit-nudge.yml b/.github/workflows/coderabbit-nudge.yml new file mode 100644 index 00000000..f7d4402e --- /dev/null +++ b/.github/workflows/coderabbit-nudge.yml @@ -0,0 +1,104 @@ +name: coderabbit-nudge + +# CodeRabbit rate-limits per developer; when it does, it posts a "Review limit +# reached" comment and only retries on its own after the stated cooldown, or +# when someone explicitly comments "@coderabbitai review". If nobody pushes a +# new commit to a stalled PR in the meantime, that comment just sits there +# forever. This is the backstop: on our own PRs, if the *last* CodeRabbit +# comment is a rate-limit notice and we haven't already nudged it since, poke +# it with "@coderabbitai review" so it wakes up on its own schedule instead of +# waiting for a human to notice. +# +# Scope is deliberately narrow: only PRs opened by the repo owner, or PRs that +# carry a "Co-Authored-By: Claude" trailer in any commit (our own automated +# work) — not every open PR in the repo. + +on: + schedule: + # Offset from the hour boundary: scheduled runs queue up right at :00 across + # all of GitHub, so a few minutes off reduces the chance of a delayed run. + - cron: "3,18,33,48 * * * *" + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + nudge: + name: wake CodeRabbit up if it rate-limited on our PRs + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + script: | + const RATE_LIMIT_MARK = "review limit reached"; + const NUDGE_BODY = "@coderabbitai review"; + const CODERABBIT_LOGIN = "coderabbitai[bot]"; + + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + per_page: 100, + }); + + for (const pr of prs) { + const isOwnerPr = pr.user?.login === context.repo.owner; + + let isOurWork = isOwnerPr; + if (!isOurWork) { + const { data: commits } = await github.rest.pulls.listCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + per_page: 100, + }); + isOurWork = commits.some((c) => + /co-authored-by:\s*claude/i.test(c.commit.message) + ); + } + if (!isOurWork) continue; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + per_page: 100, + }); + + const rabbitComments = comments.filter( + (c) => c.user?.login === CODERABBIT_LOGIN + ); + if (rabbitComments.length === 0) continue; + + const lastRabbit = rabbitComments[rabbitComments.length - 1]; + const isRateLimited = lastRabbit.body + .toLowerCase() + .includes(RATE_LIMIT_MARK); + if (!isRateLimited) { + core.info(`PR #${pr.number}: last CodeRabbit comment is not a rate-limit notice, skipping.`); + continue; + } + + const lastNudge = comments + .filter( + (c) => + c.user?.login === "github-actions[bot]" && + c.body.trim() === NUDGE_BODY + ) + .pop(); + if (lastNudge && new Date(lastNudge.created_at) > new Date(lastRabbit.created_at)) { + core.info(`PR #${pr.number}: already nudged since the last rate-limit notice, skipping.`); + continue; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: NUDGE_BODY, + }); + core.info(`PR #${pr.number}: CodeRabbit was rate-limited, posted "${NUDGE_BODY}" to wake it up.`); + } From af93d2ca28d03050f9f377c0001419652dee4c23 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 15:28:58 +0000 Subject: [PATCH 2/3] fix: paginate PR/comment/commit lists in CodeRabbit nudge workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit github.rest.*.list* calls only return the first page (max 100 items). On a PR with more than 100 comments, the unpaginated listComments call picked the "last" CodeRabbit comment from just the first page, not the actual latest one — breaking both the rate-limit detection and the already-nudged dedup check, and risking a re-nudge every run. Same truncation risk applied to the open-PR list and the per-PR commit list. Switch all three to github.paginate. Also guard lastRabbit.body with a nullish fallback so a comment body that's ever null/undefined doesn't throw and abort the loop. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01D6Naf8CcezjCbikueKdeYv --- .github/workflows/coderabbit-nudge.yml | 34 +++++++++++++++----------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/coderabbit-nudge.yml b/.github/workflows/coderabbit-nudge.yml index f7d4402e..57237ea2 100644 --- a/.github/workflows/coderabbit-nudge.yml +++ b/.github/workflows/coderabbit-nudge.yml @@ -37,7 +37,7 @@ jobs: const NUDGE_BODY = "@coderabbitai review"; const CODERABBIT_LOGIN = "coderabbitai[bot]"; - const { data: prs } = await github.rest.pulls.list({ + const prs = await github.paginate(github.rest.pulls.list, { owner: context.repo.owner, repo: context.repo.repo, state: "open", @@ -49,24 +49,30 @@ jobs: let isOurWork = isOwnerPr; if (!isOurWork) { - const { data: commits } = await github.rest.pulls.listCommits({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pr.number, - per_page: 100, - }); + const commits = await github.paginate( + github.rest.pulls.listCommits, + { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + per_page: 100, + } + ); isOurWork = commits.some((c) => /co-authored-by:\s*claude/i.test(c.commit.message) ); } if (!isOurWork) continue; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - per_page: 100, - }); + const comments = await github.paginate( + github.rest.issues.listComments, + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + per_page: 100, + } + ); const rabbitComments = comments.filter( (c) => c.user?.login === CODERABBIT_LOGIN @@ -74,7 +80,7 @@ jobs: if (rabbitComments.length === 0) continue; const lastRabbit = rabbitComments[rabbitComments.length - 1]; - const isRateLimited = lastRabbit.body + const isRateLimited = (lastRabbit.body ?? "") .toLowerCase() .includes(RATE_LIMIT_MARK); if (!isRateLimited) { From 3c3b19276bc108368e9f7006b4451852481604a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 21:03:32 +0000 Subject: [PATCH 3/3] ci: throttle the CodeRabbit nudge to hourly CodeRabbit's own cooldown is roughly an hour, so a 15-minute loop would just burn its rate-limit window (or tip into usage-based billing) without getting reviews any sooner. One nudge per hour, offset from the :00 boundary. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U --- .github/workflows/coderabbit-nudge.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coderabbit-nudge.yml b/.github/workflows/coderabbit-nudge.yml index 57237ea2..52fbef21 100644 --- a/.github/workflows/coderabbit-nudge.yml +++ b/.github/workflows/coderabbit-nudge.yml @@ -15,9 +15,12 @@ name: coderabbit-nudge on: schedule: - # Offset from the hour boundary: scheduled runs queue up right at :00 across - # all of GitHub, so a few minutes off reduces the chance of a delayed run. - - cron: "3,18,33,48 * * * *" + # Hourly, not more often: CodeRabbit's own cooldown is ~an hour, so a tighter + # loop would just burn its rate-limit window (or push us into usage-based + # billing) without getting reviews any sooner. Offset from the hour boundary: + # scheduled runs queue up right at :00 across all of GitHub, so a few minutes + # off reduces the chance of a delayed run. + - cron: "3 * * * *" workflow_dispatch: permissions: