Auto-retry ratelimited octokit requests

This commit is contained in:
Mateusz Burzyński
2026-01-07 17:10:13 +01:00
parent 0504fc42ff
commit a483711fee
9 changed files with 3759 additions and 2172 deletions
+1
View File
@@ -0,0 +1 @@
v22.14.0
+3682 -2157
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -19,6 +19,7 @@ import { fetchRepoSettings, fetchWorkflowRunInfo, type RepoSettings } from "./ut
import { log } from "./utils/cli.ts";
import { reportErrorToComment } from "./utils/errorReport.ts";
import {
createOctokit,
parseRepoContext,
revokeGitHubInstallationToken,
setupGitHubInstallationToken,
@@ -354,7 +355,7 @@ async function initializeGitHub(): Promise<GitHubSetup> {
const token = await setupGitHubInstallationToken();
const { owner, name } = parseRepoContext();
const octokit = new Octokit({ auth: token });
const octokit = createOctokit(token);
// fetch repo data and settings in parallel
const [repoResponse, repoSettings] = await Promise.all([
+4 -6
View File
@@ -1,11 +1,10 @@
import { Octokit } from "@octokit/rest";
import { type } from "arktype";
import type { Payload } from "../external.ts";
import { agentsManifest } from "../external.ts";
import type { ToolContext } from "../main.ts";
import { fetchWorkflowRunInfo } from "../utils/api.ts";
import { buildPullfrogFooter, stripExistingFooter } from "../utils/buildPullfrogFooter.ts";
import { getGitHubInstallationToken, parseRepoContext } from "../utils/github.ts";
import { createOctokit, getGitHubInstallationToken, parseRepoContext, type OctokitWithPlugins } from "../utils/github.ts";
import { execute, tool } from "./shared.ts";
/**
@@ -17,7 +16,7 @@ export const LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
interface BuildCommentFooterParams {
payload: Payload;
octokit?: Octokit | undefined;
octokit?: OctokitWithPlugins | undefined;
customParts?: string[] | undefined;
}
@@ -80,7 +79,7 @@ function buildImplementPlanLink(
return `[Implement plan ➔](${apiUrl}/trigger/${owner}/${repo}/${issueNumber}?action=implement&comment_id=${commentId})`;
}
async function addFooter(body: string, payload: Payload, octokit?: Octokit): Promise<string> {
async function addFooter(body: string, payload: Payload, octokit?: OctokitWithPlugins): Promise<string> {
const bodyWithoutFooter = stripExistingFooter(body);
const footer = await buildCommentFooter({ payload, octokit });
return `${bodyWithoutFooter}${footer}`;
@@ -408,8 +407,7 @@ export async function ensureProgressCommentUpdated(payload?: Payload): Promise<v
// check if comment still says "leaping into action" - if it's been updated with an error, don't overwrite it
const repoContext = parseRepoContext();
const token = getGitHubInstallationToken();
const octokit = new Octokit({ auth: token });
const octokit = createOctokit(getGitHubInstallationToken());
try {
const existingComment = await octokit.rest.issues.getComment({
+4 -1
View File
@@ -28,11 +28,13 @@
"@anthropic-ai/claude-agent-sdk": "0.1.37",
"@ark/fs": "0.53.0",
"@ark/util": "0.53.0",
"@octokit/plugin-throttling": "^11.0.3",
"@octokit/rest": "^22.0.0",
"@octokit/webhooks-types": "^7.6.1",
"@openai/codex-sdk": "0.58.0",
"@opencode-ai/sdk": "^1.0.143",
"@standard-schema/spec": "1.0.0",
"@toon-format/toon": "^1.0.0",
"arktype": "2.1.28",
"package-manager-detector": "^1.6.0",
"dotenv": "^17.2.3",
@@ -70,5 +72,6 @@
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
},
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
}
+40
View File
@@ -23,6 +23,9 @@ importers:
'@ark/util':
specifier: 0.53.0
version: 0.53.0
'@octokit/plugin-throttling':
specifier: ^11.0.3
version: 11.0.3(@octokit/core@7.0.5)
'@octokit/rest':
specifier: ^22.0.0
version: 22.0.0
@@ -38,6 +41,9 @@ importers:
'@standard-schema/spec':
specifier: 1.0.0
version: 1.0.0
'@toon-format/toon':
specifier: ^1.0.0
version: 1.4.0
arktype:
specifier: 2.1.28
version: 2.1.28
@@ -377,6 +383,9 @@ packages:
'@octokit/openapi-types@26.0.0':
resolution: {integrity: sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==}
'@octokit/openapi-types@27.0.0':
resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==}
'@octokit/plugin-paginate-rest@13.2.0':
resolution: {integrity: sha512-YuAlyjR8o5QoRSOvMHxSJzPtogkNMgeMv2mpccrvdUGeC3MKyfi/hS+KiFwyH/iRKIKyx+eIMsDjbt3p9r2GYA==}
engines: {node: '>= 20'}
@@ -407,6 +416,12 @@ packages:
peerDependencies:
'@octokit/core': '>=6'
'@octokit/plugin-throttling@11.0.3':
resolution: {integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==}
engines: {node: '>= 20'}
peerDependencies:
'@octokit/core': ^7.0.0
'@octokit/request-error@5.1.1':
resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==}
engines: {node: '>= 18'}
@@ -436,6 +451,9 @@ packages:
'@octokit/types@15.0.0':
resolution: {integrity: sha512-8o6yDfmoGJUIeR9OfYU0/TUJTnMPG2r68+1yEdUeG2Fdqpj8Qetg0ziKIgcBm0RW/j29H41WP37CYCEhp6GoHQ==}
'@octokit/types@16.0.0':
resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==}
'@octokit/webhooks-types@7.6.1':
resolution: {integrity: sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==}
@@ -463,6 +481,9 @@ packages:
'@tokenizer/token@0.3.0':
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
'@toon-format/toon@1.4.0':
resolution: {integrity: sha512-bjdhhIPjnX2oVk+pKy/nD3bwuESDLX/5fwW0TxwpV7Q4PVNkiRSv1S0sPeuy9TI4PfAlulow1HShdmMTnYvoLg==}
'@types/node@24.7.2':
resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==}
@@ -515,6 +536,9 @@ packages:
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
engines: {node: '>=18'}
bottleneck@2.19.5:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@@ -1347,6 +1371,8 @@ snapshots:
'@octokit/openapi-types@26.0.0': {}
'@octokit/openapi-types@27.0.0': {}
'@octokit/plugin-paginate-rest@13.2.0(@octokit/core@7.0.5)':
dependencies:
'@octokit/core': 7.0.5
@@ -1371,6 +1397,12 @@ snapshots:
'@octokit/core': 7.0.5
'@octokit/types': 15.0.0
'@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.5)':
dependencies:
'@octokit/core': 7.0.5
'@octokit/types': 16.0.0
bottleneck: 2.19.5
'@octokit/request-error@5.1.1':
dependencies:
'@octokit/types': 13.10.0
@@ -1415,6 +1447,10 @@ snapshots:
dependencies:
'@octokit/openapi-types': 26.0.0
'@octokit/types@16.0.0':
dependencies:
'@octokit/openapi-types': 27.0.0
'@octokit/webhooks-types@7.6.1': {}
'@openai/codex-sdk@0.58.0': {}
@@ -1437,6 +1473,8 @@ snapshots:
'@tokenizer/token@0.3.0': {}
'@toon-format/toon@1.4.0': {}
'@types/node@24.7.2':
dependencies:
undici-types: 7.14.0
@@ -1502,6 +1540,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
bottleneck@2.19.5: {}
bytes@3.1.2: {}
call-bind-apply-helpers@1.0.2:
+2 -4
View File
@@ -1,6 +1,5 @@
import { Octokit } from "@octokit/rest";
import { fetchWorkflowRunInfo } from "./api.ts";
import { getGitHubInstallationToken, parseRepoContext } from "./github.ts";
import { createOctokit, getGitHubInstallationToken, parseRepoContext } from "./github.ts";
/**
* Get progress comment ID from environment variable or database.
@@ -55,8 +54,7 @@ export async function reportErrorToComment({
// update comment directly using GitHub API
const repoContext = parseRepoContext();
const token = getGitHubInstallationToken();
const octokit = new Octokit({ auth: token });
const octokit = createOctokit(getGitHubInstallationToken());
await octokit.rest.issues.updateComment({
owner: repoContext.owner,
+22 -1
View File
@@ -1,5 +1,7 @@
import { createSign } from "node:crypto";
import * as core from "@actions/core";
import { throttling } from "@octokit/plugin-throttling";
import { Octokit } from "@octokit/rest";
import { createSign } from "node:crypto";
import { log } from "./cli.ts";
import { retry } from "./retry.ts";
@@ -311,3 +313,22 @@ export function parseRepoContext(): RepoContext {
return { owner, name };
}
export type OctokitWithPlugins = InstanceType<ReturnType<typeof Octokit.plugin<typeof Octokit, [typeof throttling]>>>
export function createOctokit(token: string): OctokitWithPlugins {
// `OctokitWithPlugins` initialization based on https://github.com/actions/toolkit/blob/2506e78e82fbd2f9e94d63e75f5309118c8de1b1/packages/github/src/github.ts#L15-L22
// we can't use it directly because it's stuck on `@octokit/core@v5` and we use the hottest `@octokit/core@v7`
const OctokitWithPlugins = Octokit.plugin(throttling);
return new OctokitWithPlugins({
auth: token,
throttle: {
onRateLimit: (retryAfter, options, octokit, retryCount) => {
return retryCount <= 2;
},
onSecondaryRateLimit: (retryAfter, options, octokit, retryCount) => {
return retryCount <= 2;
},
}
});
}
+2 -2
View File
@@ -1,10 +1,10 @@
import { execSync } from "node:child_process";
import { existsSync, rmSync } from "node:fs";
import type { Octokit } from "@octokit/rest";
import type { Payload } from "../external.ts";
import type { ToolState } from "../main.ts";
import { checkoutPrBranch } from "../mcp/checkout.ts";
import { log } from "./cli.ts";
import type { OctokitWithPlugins } from "./github.ts";
import { $ } from "./shell.ts";
export interface SetupOptions {
@@ -66,7 +66,7 @@ interface SetupGitAuthParams {
owner: string;
name: string;
payload: Payload;
octokit: Octokit;
octokit: OctokitWithPlugins;
toolState: ToolState;
}