Skip to content

Commit c0f553f

Browse files
authored
feat: add @octokit/plugin-retry to handle retriable server errors (#4298)
Add the retry plugin to automatically retry requests that fail with server errors (5xx status codes). Configure the plugin to exclude 429 (rate limit) from retries since that is already handled by the throttling plugin. - Add @octokit/plugin-retry dependency - Register retry plugin in Octokit client - Export retryOptions with doNotRetry list excluding 429 - Apply retryOptions in GitHubHelper constructor
1 parent 7000124 commit c0f553f

File tree

5 files changed

+205
-49
lines changed

5 files changed

+205
-49
lines changed

dist/index.js

Lines changed: 157 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,7 @@ class GitHubHelper {
13951395
options.baseUrl = 'https://api.github.com';
13961396
}
13971397
options.throttle = octokit_client_1.throttleOptions;
1398+
options.retry = octokit_client_1.retryOptions;
13981399
this.octokit = new octokit_client_1.Octokit(options);
13991400
}
14001401
parseRepository(repository) {
@@ -1824,14 +1825,15 @@ var __importStar = (this && this.__importStar) || (function () {
18241825
};
18251826
})();
18261827
Object.defineProperty(exports, "__esModule", ({ value: true }));
1827-
exports.throttleOptions = exports.Octokit = void 0;
1828+
exports.retryOptions = exports.throttleOptions = exports.Octokit = void 0;
18281829
const core = __importStar(__nccwpck_require__(7484));
1829-
const core_1 = __nccwpck_require__(767);
1830+
const core_1 = __nccwpck_require__(708);
18301831
const plugin_paginate_rest_1 = __nccwpck_require__(3779);
18311832
const plugin_rest_endpoint_methods_1 = __nccwpck_require__(9210);
1833+
const plugin_retry_1 = __nccwpck_require__(9735);
18321834
const plugin_throttling_1 = __nccwpck_require__(6856);
18331835
const proxy_1 = __nccwpck_require__(3459);
1834-
exports.Octokit = core_1.Octokit.plugin(plugin_paginate_rest_1.paginateRest, plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_throttling_1.throttling, autoProxyAgent);
1836+
exports.Octokit = core_1.Octokit.plugin(plugin_paginate_rest_1.paginateRest, plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_retry_1.retry, plugin_throttling_1.throttling, autoProxyAgent);
18351837
exports.throttleOptions = {
18361838
onRateLimit: (retryAfter, options, _, retryCount) => {
18371839
core.debug(`Hit rate limit for request ${options.method} ${options.url}`);
@@ -1846,6 +1848,10 @@ exports.throttleOptions = {
18461848
core.warning(`Requests may be retried after ${retryAfter} seconds.`);
18471849
}
18481850
};
1851+
exports.retryOptions = {
1852+
// 429 is handled by the throttling plugin, so we exclude it from retry
1853+
doNotRetry: [400, 401, 403, 404, 410, 422, 429, 451]
1854+
};
18491855
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
18501856
function autoProxyAgent(octokit) {
18511857
octokit.hook.before('request', options => {
@@ -32221,7 +32227,7 @@ module.exports = fetch;
3222132227

3222232228
/***/ }),
3222332229

32224-
/***/ 767:
32230+
/***/ 708:
3222532231
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
3222632232

3222732233
"use strict";
@@ -32741,46 +32747,8 @@ var endpoint = withDefaults(null, DEFAULTS);
3274132747

3274232748
// EXTERNAL MODULE: ./node_modules/fast-content-type-parse/index.js
3274332749
var fast_content_type_parse = __nccwpck_require__(8739);
32744-
;// CONCATENATED MODULE: ./node_modules/@octokit/request-error/dist-src/index.js
32745-
class RequestError extends Error {
32746-
name;
32747-
/**
32748-
* http status code
32749-
*/
32750-
status;
32751-
/**
32752-
* Request options that lead to the error.
32753-
*/
32754-
request;
32755-
/**
32756-
* Response object if a response was received
32757-
*/
32758-
response;
32759-
constructor(message, statusCode, options) {
32760-
super(message);
32761-
this.name = "HttpError";
32762-
this.status = Number.parseInt(statusCode);
32763-
if (Number.isNaN(this.status)) {
32764-
this.status = 0;
32765-
}
32766-
if ("response" in options) {
32767-
this.response = options.response;
32768-
}
32769-
const requestCopy = Object.assign({}, options.request);
32770-
if (options.request.headers.authorization) {
32771-
requestCopy.headers = Object.assign({}, options.request.headers, {
32772-
authorization: options.request.headers.authorization.replace(
32773-
/(?<! ) .*$/,
32774-
" [REDACTED]"
32775-
)
32776-
});
32777-
}
32778-
requestCopy.url = requestCopy.url.replace(/\bclient_secret=\w+/g, "client_secret=[REDACTED]").replace(/\baccess_token=\w+/g, "access_token=[REDACTED]");
32779-
this.request = requestCopy;
32780-
}
32781-
}
32782-
32783-
32750+
// EXTERNAL MODULE: ./node_modules/@octokit/request-error/dist-src/index.js
32751+
var dist_src = __nccwpck_require__(1015);
3278432752
;// CONCATENATED MODULE: ./node_modules/@octokit/request/dist-bundle/index.js
3278532753
// pkg/dist-src/index.js
3278632754

@@ -32857,7 +32825,7 @@ async function fetchWrapper(requestOptions) {
3285732825
}
3285832826
}
3285932827
}
32860-
const requestError = new RequestError(message, 500, {
32828+
const requestError = new dist_src/* RequestError */.G(message, 500, {
3286132829
request: requestOptions
3286232830
});
3286332831
requestError.cause = error;
@@ -32889,21 +32857,21 @@ async function fetchWrapper(requestOptions) {
3288932857
if (status < 400) {
3289032858
return octokitResponse;
3289132859
}
32892-
throw new RequestError(fetchResponse.statusText, status, {
32860+
throw new dist_src/* RequestError */.G(fetchResponse.statusText, status, {
3289332861
response: octokitResponse,
3289432862
request: requestOptions
3289532863
});
3289632864
}
3289732865
if (status === 304) {
3289832866
octokitResponse.data = await getResponseData(fetchResponse);
32899-
throw new RequestError("Not modified", status, {
32867+
throw new dist_src/* RequestError */.G("Not modified", status, {
3290032868
response: octokitResponse,
3290132869
request: requestOptions
3290232870
});
3290332871
}
3290432872
if (status >= 400) {
3290532873
octokitResponse.data = await getResponseData(fetchResponse);
32906-
throw new RequestError(toErrorMessage(octokitResponse.data), status, {
32874+
throw new dist_src/* RequestError */.G(toErrorMessage(octokitResponse.data), status, {
3290732875
response: octokitResponse,
3290832876
request: requestOptions
3290932877
});
@@ -36200,6 +36168,98 @@ legacyRestEndpointMethods.VERSION = VERSION;
3620036168
//# sourceMappingURL=index.js.map
3620136169

3620236170

36171+
/***/ }),
36172+
36173+
/***/ 9735:
36174+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
36175+
36176+
"use strict";
36177+
__nccwpck_require__.r(__webpack_exports__);
36178+
/* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
36179+
/* harmony export */ VERSION: () => (/* binding */ VERSION),
36180+
/* harmony export */ retry: () => (/* binding */ retry)
36181+
/* harmony export */ });
36182+
/* harmony import */ var bottleneck_light_js__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(3251);
36183+
/* harmony import */ var _octokit_request_error__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(1015);
36184+
// pkg/dist-src/version.js
36185+
var VERSION = "0.0.0-development";
36186+
36187+
// pkg/dist-src/error-request.js
36188+
async function errorRequest(state, octokit, error, options) {
36189+
if (!error.request || !error.request.request) {
36190+
throw error;
36191+
}
36192+
if (error.status >= 400 && !state.doNotRetry.includes(error.status)) {
36193+
const retries = options.request.retries != null ? options.request.retries : state.retries;
36194+
const retryAfter = Math.pow((options.request.retryCount || 0) + 1, 2);
36195+
throw octokit.retry.retryRequest(error, retries, retryAfter);
36196+
}
36197+
throw error;
36198+
}
36199+
36200+
// pkg/dist-src/wrap-request.js
36201+
36202+
36203+
async function wrapRequest(state, octokit, request, options) {
36204+
const limiter = new bottleneck_light_js__WEBPACK_IMPORTED_MODULE_0__();
36205+
limiter.on("failed", function(error, info) {
36206+
const maxRetries = ~~error.request.request.retries;
36207+
const after = ~~error.request.request.retryAfter;
36208+
options.request.retryCount = info.retryCount + 1;
36209+
if (maxRetries > info.retryCount) {
36210+
return after * state.retryAfterBaseValue;
36211+
}
36212+
});
36213+
return limiter.schedule(
36214+
requestWithGraphqlErrorHandling.bind(null, state, octokit, request),
36215+
options
36216+
);
36217+
}
36218+
async function requestWithGraphqlErrorHandling(state, octokit, request, options) {
36219+
const response = await request(request, options);
36220+
if (response.data && response.data.errors && response.data.errors.length > 0 && /Something went wrong while executing your query/.test(
36221+
response.data.errors[0].message
36222+
)) {
36223+
const error = new _octokit_request_error__WEBPACK_IMPORTED_MODULE_1__/* .RequestError */ .G(response.data.errors[0].message, 500, {
36224+
request: options,
36225+
response
36226+
});
36227+
return errorRequest(state, octokit, error, options);
36228+
}
36229+
return response;
36230+
}
36231+
36232+
// pkg/dist-src/index.js
36233+
function retry(octokit, octokitOptions) {
36234+
const state = Object.assign(
36235+
{
36236+
enabled: true,
36237+
retryAfterBaseValue: 1e3,
36238+
doNotRetry: [400, 401, 403, 404, 410, 422, 451],
36239+
retries: 3
36240+
},
36241+
octokitOptions.retry
36242+
);
36243+
if (state.enabled) {
36244+
octokit.hook.error("request", errorRequest.bind(null, state, octokit));
36245+
octokit.hook.wrap("request", wrapRequest.bind(null, state, octokit));
36246+
}
36247+
return {
36248+
retry: {
36249+
retryRequest: (error, retries, retryAfter) => {
36250+
error.request.request = Object.assign({}, error.request.request, {
36251+
retries,
36252+
retryAfter
36253+
});
36254+
return error;
36255+
}
36256+
}
36257+
};
36258+
}
36259+
retry.VERSION = VERSION;
36260+
36261+
36262+
3620336263
/***/ }),
3620436264

3620536265
/***/ 6856:
@@ -36439,6 +36499,55 @@ throttling.triggersNotification = triggersNotification;
3643936499

3644036500

3644136501

36502+
/***/ }),
36503+
36504+
/***/ 1015:
36505+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
36506+
36507+
"use strict";
36508+
/* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
36509+
/* harmony export */ G: () => (/* binding */ RequestError)
36510+
/* harmony export */ });
36511+
class RequestError extends Error {
36512+
name;
36513+
/**
36514+
* http status code
36515+
*/
36516+
status;
36517+
/**
36518+
* Request options that lead to the error.
36519+
*/
36520+
request;
36521+
/**
36522+
* Response object if a response was received
36523+
*/
36524+
response;
36525+
constructor(message, statusCode, options) {
36526+
super(message);
36527+
this.name = "HttpError";
36528+
this.status = Number.parseInt(statusCode);
36529+
if (Number.isNaN(this.status)) {
36530+
this.status = 0;
36531+
}
36532+
if ("response" in options) {
36533+
this.response = options.response;
36534+
}
36535+
const requestCopy = Object.assign({}, options.request);
36536+
if (options.request.headers.authorization) {
36537+
requestCopy.headers = Object.assign({}, options.request.headers, {
36538+
authorization: options.request.headers.authorization.replace(
36539+
/(?<! ) .*$/,
36540+
" [REDACTED]"
36541+
)
36542+
});
36543+
}
36544+
requestCopy.url = requestCopy.url.replace(/\bclient_secret=\w+/g, "client_secret=[REDACTED]").replace(/\baccess_token=\w+/g, "access_token=[REDACTED]");
36545+
this.request = requestCopy;
36546+
}
36547+
}
36548+
36549+
36550+
3644236551
/***/ }),
3644336552

3644436553
/***/ 7989:

package-lock.json

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@octokit/core": "^6.1.6",
3838
"@octokit/plugin-paginate-rest": "^11.6.0",
3939
"@octokit/plugin-rest-endpoint-methods": "^13.5.0",
40+
"@octokit/plugin-retry": "^7.2.1",
4041
"@octokit/plugin-throttling": "^9.6.1",
4142
"node-fetch-native": "^1.6.7",
4243
"p-limit": "^6.2.0",

src/github-helper.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import * as core from '@actions/core'
22
import {Inputs} from './create-pull-request'
33
import {Commit, GitCommandManager} from './git-command-manager'
4-
import {Octokit, OctokitOptions, throttleOptions} from './octokit-client'
4+
import {
5+
Octokit,
6+
OctokitOptions,
7+
retryOptions,
8+
throttleOptions
9+
} from './octokit-client'
510
import pLimit from 'p-limit'
611
import * as utils from './utils'
712

@@ -52,6 +57,7 @@ export class GitHubHelper {
5257
options.baseUrl = 'https://api.github.com'
5358
}
5459
options.throttle = throttleOptions
60+
options.retry = retryOptions
5561
this.octokit = new Octokit(options)
5662
}
5763

src/octokit-client.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as core from '@actions/core'
22
import {Octokit as OctokitCore} from '@octokit/core'
33
import {paginateRest} from '@octokit/plugin-paginate-rest'
44
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
5+
import {retry} from '@octokit/plugin-retry'
56
import {throttling} from '@octokit/plugin-throttling'
67
import {fetch} from 'node-fetch-native/proxy'
78
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
@@ -11,6 +12,7 @@ export {OctokitOptions} from '@octokit/core/dist-types/types'
1112
export const Octokit = OctokitCore.plugin(
1213
paginateRest,
1314
restEndpointMethods,
15+
retry,
1416
throttling,
1517
autoProxyAgent
1618
)
@@ -32,6 +34,11 @@ export const throttleOptions = {
3234
}
3335
}
3436

37+
export const retryOptions = {
38+
// 429 is handled by the throttling plugin, so we exclude it from retry
39+
doNotRetry: [400, 401, 403, 404, 410, 422, 429, 451]
40+
}
41+
3542
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
3643
function autoProxyAgent(octokit: OctokitCore) {
3744
octokit.hook.before('request', options => {

0 commit comments

Comments
 (0)