diff --git a/.github/workflows/coderabbit-rate-limit-retry.yml b/.github/workflows/coderabbit-rate-limit-retry.yml new file mode 100644 index 0000000000..454bff8ea6 --- /dev/null +++ b/.github/workflows/coderabbit-rate-limit-retry.yml @@ -0,0 +1,229 @@ +name: coderabbit-rate-limit-retry + +on: + pull_request_target: + types: [opened, synchronize, reopened] + schedule: + - cron: '*/20 * * * *' + workflow_dispatch: + +permissions: + checks: write + contents: read + pull-requests: write + issues: write + +jobs: + retrigger: + name: retrigger-coderabbit-on-rate-limit + runs-on: ubuntu-latest + steps: + - name: Re-request CodeRabbit when backlog is high and check is stale + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const STALE_MINUTES = 20; + const BACKLOG_THRESHOLD = 10; + const BYPASS_LABEL = "ci:coderabbit-bypass"; + const GATE_CHECK_NAME = "CodeRabbit Gate"; + const MARKER = ""; + + const nowMs = Date.now(); + + async function listOpenPRs() { + const all = await github.paginate(github.rest.pulls.list, { + owner, + repo, + state: "open", + per_page: 100, + }); + return all; + } + + async function getCodeRabbitState(prNumber) { + const checks = await github.graphql( + `query($owner:String!,$repo:String!,$number:Int!){ + repository(owner:$owner,name:$repo){ + pullRequest(number:$number){ + commits(last:1){ + nodes{ + commit{ + statusCheckRollup{ + contexts(first:50){ + nodes{ + __typename + ... on CheckRun { + name + conclusion + status + completedAt + } + ... on StatusContext { + context + state + createdAt + } + } + } + } + } + } + } + } + } + }`, + { owner, repo, number: prNumber }, + ); + + const nodes = checks.repository.pullRequest.commits.nodes[0]?.commit?.statusCheckRollup?.contexts?.nodes || []; + for (const n of nodes) { + if (n.__typename === "CheckRun" && n.name === "CodeRabbit") { + return { + state: (n.conclusion || n.status || "UNKNOWN").toUpperCase(), + at: n.completedAt ? new Date(n.completedAt).getTime() : nowMs, + }; + } + if (n.__typename === "StatusContext" && n.context === "CodeRabbit") { + return { + state: (n.state || "UNKNOWN").toUpperCase(), + at: n.createdAt ? new Date(n.createdAt).getTime() : nowMs, + }; + } + } + return { state: "MISSING", at: nowMs }; + } + + async function hasRecentRetryComment(prNumber) { + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: prNumber, + per_page: 100, + }); + + const latest = comments + .filter((c) => c.user?.login === "github-actions[bot]" && c.body?.includes(MARKER)) + .sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0]; + + if (!latest) return false; + const ageMin = (nowMs - new Date(latest.created_at).getTime()) / 60000; + return ageMin < STALE_MINUTES; + } + + async function ensureBypassLabelExists() { + try { + await github.rest.issues.getLabel({ + owner, + repo, + name: BYPASS_LABEL, + }); + } catch (error) { + if (error.status !== 404) throw error; + await github.rest.issues.createLabel({ + owner, + repo, + name: BYPASS_LABEL, + color: "B60205", + description: "Temporary bypass for CodeRabbit rate-limit under high PR backlog.", + }); + } + } + + async function hasLabel(prNumber, name) { + const labels = await github.paginate(github.rest.issues.listLabelsOnIssue, { + owner, + repo, + issue_number: prNumber, + per_page: 100, + }); + return labels.some((l) => l.name === name); + } + + async function setBypassLabel(prNumber, enable) { + const present = await hasLabel(prNumber, BYPASS_LABEL); + if (enable && !present) { + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: prNumber, + labels: [BYPASS_LABEL], + }); + core.notice(`PR #${prNumber}: applied label '${BYPASS_LABEL}'.`); + } + if (!enable && present) { + await github.rest.issues.removeLabel({ + owner, + repo, + issue_number: prNumber, + name: BYPASS_LABEL, + }); + core.notice(`PR #${prNumber}: removed label '${BYPASS_LABEL}'.`); + } + } + + async function publishGate(pr, pass, summary) { + await github.rest.checks.create({ + owner, + repo, + name: GATE_CHECK_NAME, + head_sha: pr.head.sha, + status: "completed", + conclusion: pass ? "success" : "failure", + output: { + title: pass ? "CodeRabbit gate passed" : "CodeRabbit gate blocked", + summary, + }, + }); + } + + async function processPR(pr) { + const state = await getCodeRabbitState(pr.number); + const ageMin = (nowMs - state.at) / 60000; + const stateOk = state.state === "SUCCESS" || state.state === "NEUTRAL"; + const stale = ageMin >= STALE_MINUTES; + const backlogHigh = openPRs.length > BACKLOG_THRESHOLD; + const bypassEligible = backlogHigh && stale && !stateOk; + + await setBypassLabel(pr.number, bypassEligible); + + if (bypassEligible && !(await hasRecentRetryComment(pr.number))) { + const body = [ + MARKER, + "@coderabbitai full review", + "", + `Automated retrigger: backlog > ${BACKLOG_THRESHOLD}, CodeRabbit state=${state.state}, age=${ageMin.toFixed(1)}m.`, + ].join("\n"); + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pr.number, + body, + }); + + core.notice(`PR #${pr.number}: posted CodeRabbit retrigger comment.`); + } + + const gatePass = stateOk || bypassEligible; + const summary = [ + `CodeRabbit state: ${state.state}`, + `Age minutes: ${ageMin.toFixed(1)}`, + `Open PR backlog: ${openPRs.length}`, + `Bypass eligible: ${bypassEligible}`, + ].join("\n"); + await publishGate(pr, gatePass, summary); + } + + const openPRs = await listOpenPRs(); + core.info(`Open PR count: ${openPRs.length}`); + await ensureBypassLabelExists(); + + const targetPRs = context.eventName === "pull_request_target" + ? openPRs.filter((p) => p.number === context.payload.pull_request.number) + : openPRs; + + for (const pr of targetPRs) { + await processPR(pr); + } diff --git a/sdk/auth/github_copilot.go b/sdk/auth/github_copilot.go index 5bb507e451..031192cf9d 100644 --- a/sdk/auth/github_copilot.go +++ b/sdk/auth/github_copilot.go @@ -86,8 +86,6 @@ func (a GitHubCopilotAuthenticator) Login(ctx context.Context, cfg *config.Confi metadata := map[string]any{ "type": "github-copilot", "username": authBundle.Username, - "email": authBundle.Email, - "name": authBundle.Name, "access_token": authBundle.TokenData.AccessToken, "token_type": authBundle.TokenData.TokenType, "scope": authBundle.TokenData.Scope, @@ -100,10 +98,7 @@ func (a GitHubCopilotAuthenticator) Login(ctx context.Context, cfg *config.Confi fileName := fmt.Sprintf("github-copilot-%s.json", authBundle.Username) - label := authBundle.Email - if label == "" { - label = authBundle.Username - } + label := authBundle.Username fmt.Printf("\nGitHub Copilot authentication successful for user: %s\n", authBundle.Username)