fix(scout-bot): add GitHub rate-limit backoff before continuing scans
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s

This commit is contained in:
OG T
2026-06-07 15:41:33 +08:00
parent a7b8ffbfbe
commit 9f78778132

View File

@@ -15,6 +15,7 @@ const SCOUT_TARGET_REPOS_RAW =
"open-webui/open-webui,microsoft/vscode,vercel/next.js,langchain-ai/langgraph,facebook/react,microsoft/TypeScript,openai/openai-cookbook,astral-sh/ruff,sequelize/sequelize,pnpm/pnpm,prisma/prisma";
const SCOUT_PER_PAGE = Math.min(Math.max(parseInt(process.env.SCOUT_PER_PAGE || "50", 10), 1), 100);
const SCOUT_MAX_ISSUES_PER_SCAN = Math.max(parseInt(process.env.SCOUT_MAX_ISSUES_PER_SCAN || "60", 10), 1);
const SCOUT_COMMENT_DELAY_SECONDS = Math.max(parseInt(process.env.SCOUT_COMMENT_DELAY_SECONDS || "2", 10), 0);
const SCOUT_ISSUE_LABELS = SCOUT_ISSUE_LABELS_RAW
.split(",")
@@ -77,6 +78,72 @@ async function wait(seconds: number) {
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
function getGitHubBackoffSeconds(error: unknown): number {
const status =
typeof error === "object" &&
error !== null &&
"status" in error &&
typeof (error as { status?: unknown }).status === "number"
? (error as { status: number }).status
: undefined;
const message =
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof (error as { message?: unknown }).message === "string"
? (error as { message: string }).message.toLowerCase()
: "";
const responseHeaders =
typeof error === "object" &&
error !== null &&
"response" in error &&
error.response &&
typeof error.response === "object" &&
"headers" in error.response
? (error.response as { headers?: Record<string, string> }).headers
: undefined;
const lowerCaseHeaders = responseHeaders
? Object.fromEntries(
Object.entries(responseHeaders).map(([key, value]) => [key.toLowerCase(), value])
)
: undefined;
if (
typeof status === "number" &&
status === 403 &&
message.includes("secondary rate limit")
) {
const resetAtRaw = lowerCaseHeaders?.["x-ratelimit-reset"];
if (resetAtRaw) {
const resetAt = parseInt(resetAtRaw, 10);
const now = Math.floor(Date.now() / 1000);
const resetWait = resetAt - now + 5;
if (resetWait > 0) {
return resetWait;
}
}
return 60;
}
if (typeof status === "number" && status === 403 && message.includes("rate limit exceeded")) {
const resetAtRaw = lowerCaseHeaders?.["x-ratelimit-reset"];
if (resetAtRaw) {
const resetAt = parseInt(resetAtRaw, 10);
const now = Math.floor(Date.now() / 1000);
const resetWait = resetAt - now + 5;
if (resetWait > 0) {
return resetWait;
}
}
return 60;
}
return 0;
}
async function postDraftWithRetry(payload: {
scout_id: string;
title: string;
@@ -215,7 +282,13 @@ npx -y @agent-bounty/mcp-server --endpoint https://agent.wooo.work
body: commentBody,
});
console.log(`Successfully commented on #${issue.number}`);
await wait(SCOUT_COMMENT_DELAY_SECONDS);
} catch (error) {
const backoffSeconds = getGitHubBackoffSeconds(error);
if (backoffSeconds > 0) {
console.warn(`GitHub API rate limited; sleeping ${backoffSeconds}s before continuing.`);
await wait(backoffSeconds);
}
console.error(`Failed to comment on #${issue.number}:`, error);
}
} else {
@@ -229,7 +302,7 @@ async function scanRepositories() {
for (const target of TARGET_REPOS) {
try {
const issues = await octokit.issues.listForRepo({
const issues = await octokit.issues.listForRepo({
owner: target.owner,
repo: target.repo,
state: "open",
@@ -250,6 +323,12 @@ async function scanRepositories() {
console.log(`Limited scan to first ${issueCandidates.length} issues in ${target.owner}/${target.repo}`);
}
} catch (error) {
const backoffSeconds = getGitHubBackoffSeconds(error);
if (backoffSeconds > 0) {
console.warn(`GitHub API rate limited on ${target.owner}/${target.repo}; sleeping ${backoffSeconds}s and skip remaining repos.`);
await wait(backoffSeconds);
return;
}
console.error(`Error scanning ${target.owner}/${target.repo}:`, error);
}
}