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
This commit is contained in:
Peter Evans
2026-01-21 15:20:27 +00:00
committed by GitHub
parent 70001242bf
commit c0f553fe54
5 changed files with 205 additions and 49 deletions

205
dist/index.js vendored
View File

@@ -1395,6 +1395,7 @@ class GitHubHelper {
options.baseUrl = 'https://api.github.com'; options.baseUrl = 'https://api.github.com';
} }
options.throttle = octokit_client_1.throttleOptions; options.throttle = octokit_client_1.throttleOptions;
options.retry = octokit_client_1.retryOptions;
this.octokit = new octokit_client_1.Octokit(options); this.octokit = new octokit_client_1.Octokit(options);
} }
parseRepository(repository) { parseRepository(repository) {
@@ -1824,14 +1825,15 @@ var __importStar = (this && this.__importStar) || (function () {
}; };
})(); })();
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.throttleOptions = exports.Octokit = void 0; exports.retryOptions = exports.throttleOptions = exports.Octokit = void 0;
const core = __importStar(__nccwpck_require__(7484)); const core = __importStar(__nccwpck_require__(7484));
const core_1 = __nccwpck_require__(767); const core_1 = __nccwpck_require__(708);
const plugin_paginate_rest_1 = __nccwpck_require__(3779); const plugin_paginate_rest_1 = __nccwpck_require__(3779);
const plugin_rest_endpoint_methods_1 = __nccwpck_require__(9210); const plugin_rest_endpoint_methods_1 = __nccwpck_require__(9210);
const plugin_retry_1 = __nccwpck_require__(9735);
const plugin_throttling_1 = __nccwpck_require__(6856); const plugin_throttling_1 = __nccwpck_require__(6856);
const proxy_1 = __nccwpck_require__(3459); const proxy_1 = __nccwpck_require__(3459);
exports.Octokit = core_1.Octokit.plugin(plugin_paginate_rest_1.paginateRest, plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_throttling_1.throttling, autoProxyAgent); 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);
exports.throttleOptions = { exports.throttleOptions = {
onRateLimit: (retryAfter, options, _, retryCount) => { onRateLimit: (retryAfter, options, _, retryCount) => {
core.debug(`Hit rate limit for request ${options.method} ${options.url}`); core.debug(`Hit rate limit for request ${options.method} ${options.url}`);
@@ -1846,6 +1848,10 @@ exports.throttleOptions = {
core.warning(`Requests may be retried after ${retryAfter} seconds.`); core.warning(`Requests may be retried after ${retryAfter} seconds.`);
} }
}; };
exports.retryOptions = {
// 429 is handled by the throttling plugin, so we exclude it from retry
doNotRetry: [400, 401, 403, 404, 410, 422, 429, 451]
};
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy // Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
function autoProxyAgent(octokit) { function autoProxyAgent(octokit) {
octokit.hook.before('request', options => { octokit.hook.before('request', options => {
@@ -32221,7 +32227,7 @@ module.exports = fetch;
/***/ }), /***/ }),
/***/ 767: /***/ 708:
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => { /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
"use strict"; "use strict";
@@ -32741,46 +32747,8 @@ var endpoint = withDefaults(null, DEFAULTS);
// EXTERNAL MODULE: ./node_modules/fast-content-type-parse/index.js // EXTERNAL MODULE: ./node_modules/fast-content-type-parse/index.js
var fast_content_type_parse = __nccwpck_require__(8739); var fast_content_type_parse = __nccwpck_require__(8739);
;// CONCATENATED MODULE: ./node_modules/@octokit/request-error/dist-src/index.js // EXTERNAL MODULE: ./node_modules/@octokit/request-error/dist-src/index.js
class RequestError extends Error { var dist_src = __nccwpck_require__(1015);
name;
/**
* http status code
*/
status;
/**
* Request options that lead to the error.
*/
request;
/**
* Response object if a response was received
*/
response;
constructor(message, statusCode, options) {
super(message);
this.name = "HttpError";
this.status = Number.parseInt(statusCode);
if (Number.isNaN(this.status)) {
this.status = 0;
}
if ("response" in options) {
this.response = options.response;
}
const requestCopy = Object.assign({}, options.request);
if (options.request.headers.authorization) {
requestCopy.headers = Object.assign({}, options.request.headers, {
authorization: options.request.headers.authorization.replace(
/(?<! ) .*$/,
" [REDACTED]"
)
});
}
requestCopy.url = requestCopy.url.replace(/\bclient_secret=\w+/g, "client_secret=[REDACTED]").replace(/\baccess_token=\w+/g, "access_token=[REDACTED]");
this.request = requestCopy;
}
}
;// CONCATENATED MODULE: ./node_modules/@octokit/request/dist-bundle/index.js ;// CONCATENATED MODULE: ./node_modules/@octokit/request/dist-bundle/index.js
// pkg/dist-src/index.js // pkg/dist-src/index.js
@@ -32857,7 +32825,7 @@ async function fetchWrapper(requestOptions) {
} }
} }
} }
const requestError = new RequestError(message, 500, { const requestError = new dist_src/* RequestError */.G(message, 500, {
request: requestOptions request: requestOptions
}); });
requestError.cause = error; requestError.cause = error;
@@ -32889,21 +32857,21 @@ async function fetchWrapper(requestOptions) {
if (status < 400) { if (status < 400) {
return octokitResponse; return octokitResponse;
} }
throw new RequestError(fetchResponse.statusText, status, { throw new dist_src/* RequestError */.G(fetchResponse.statusText, status, {
response: octokitResponse, response: octokitResponse,
request: requestOptions request: requestOptions
}); });
} }
if (status === 304) { if (status === 304) {
octokitResponse.data = await getResponseData(fetchResponse); octokitResponse.data = await getResponseData(fetchResponse);
throw new RequestError("Not modified", status, { throw new dist_src/* RequestError */.G("Not modified", status, {
response: octokitResponse, response: octokitResponse,
request: requestOptions request: requestOptions
}); });
} }
if (status >= 400) { if (status >= 400) {
octokitResponse.data = await getResponseData(fetchResponse); octokitResponse.data = await getResponseData(fetchResponse);
throw new RequestError(toErrorMessage(octokitResponse.data), status, { throw new dist_src/* RequestError */.G(toErrorMessage(octokitResponse.data), status, {
response: octokitResponse, response: octokitResponse,
request: requestOptions request: requestOptions
}); });
@@ -36200,6 +36168,98 @@ legacyRestEndpointMethods.VERSION = VERSION;
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map
/***/ }),
/***/ 9735:
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
"use strict";
__nccwpck_require__.r(__webpack_exports__);
/* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
/* harmony export */ VERSION: () => (/* binding */ VERSION),
/* harmony export */ retry: () => (/* binding */ retry)
/* harmony export */ });
/* harmony import */ var bottleneck_light_js__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(3251);
/* harmony import */ var _octokit_request_error__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(1015);
// pkg/dist-src/version.js
var VERSION = "0.0.0-development";
// pkg/dist-src/error-request.js
async function errorRequest(state, octokit, error, options) {
if (!error.request || !error.request.request) {
throw error;
}
if (error.status >= 400 && !state.doNotRetry.includes(error.status)) {
const retries = options.request.retries != null ? options.request.retries : state.retries;
const retryAfter = Math.pow((options.request.retryCount || 0) + 1, 2);
throw octokit.retry.retryRequest(error, retries, retryAfter);
}
throw error;
}
// pkg/dist-src/wrap-request.js
async function wrapRequest(state, octokit, request, options) {
const limiter = new bottleneck_light_js__WEBPACK_IMPORTED_MODULE_0__();
limiter.on("failed", function(error, info) {
const maxRetries = ~~error.request.request.retries;
const after = ~~error.request.request.retryAfter;
options.request.retryCount = info.retryCount + 1;
if (maxRetries > info.retryCount) {
return after * state.retryAfterBaseValue;
}
});
return limiter.schedule(
requestWithGraphqlErrorHandling.bind(null, state, octokit, request),
options
);
}
async function requestWithGraphqlErrorHandling(state, octokit, request, options) {
const response = await request(request, options);
if (response.data && response.data.errors && response.data.errors.length > 0 && /Something went wrong while executing your query/.test(
response.data.errors[0].message
)) {
const error = new _octokit_request_error__WEBPACK_IMPORTED_MODULE_1__/* .RequestError */ .G(response.data.errors[0].message, 500, {
request: options,
response
});
return errorRequest(state, octokit, error, options);
}
return response;
}
// pkg/dist-src/index.js
function retry(octokit, octokitOptions) {
const state = Object.assign(
{
enabled: true,
retryAfterBaseValue: 1e3,
doNotRetry: [400, 401, 403, 404, 410, 422, 451],
retries: 3
},
octokitOptions.retry
);
if (state.enabled) {
octokit.hook.error("request", errorRequest.bind(null, state, octokit));
octokit.hook.wrap("request", wrapRequest.bind(null, state, octokit));
}
return {
retry: {
retryRequest: (error, retries, retryAfter) => {
error.request.request = Object.assign({}, error.request.request, {
retries,
retryAfter
});
return error;
}
}
};
}
retry.VERSION = VERSION;
/***/ }), /***/ }),
/***/ 6856: /***/ 6856:
@@ -36439,6 +36499,55 @@ throttling.triggersNotification = triggersNotification;
/***/ }),
/***/ 1015:
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
"use strict";
/* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
/* harmony export */ G: () => (/* binding */ RequestError)
/* harmony export */ });
class RequestError extends Error {
name;
/**
* http status code
*/
status;
/**
* Request options that lead to the error.
*/
request;
/**
* Response object if a response was received
*/
response;
constructor(message, statusCode, options) {
super(message);
this.name = "HttpError";
this.status = Number.parseInt(statusCode);
if (Number.isNaN(this.status)) {
this.status = 0;
}
if ("response" in options) {
this.response = options.response;
}
const requestCopy = Object.assign({}, options.request);
if (options.request.headers.authorization) {
requestCopy.headers = Object.assign({}, options.request.headers, {
authorization: options.request.headers.authorization.replace(
/(?<! ) .*$/,
" [REDACTED]"
)
});
}
requestCopy.url = requestCopy.url.replace(/\bclient_secret=\w+/g, "client_secret=[REDACTED]").replace(/\baccess_token=\w+/g, "access_token=[REDACTED]");
this.request = requestCopy;
}
}
/***/ }), /***/ }),
/***/ 7989: /***/ 7989:

33
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@octokit/core": "^6.1.6", "@octokit/core": "^6.1.6",
"@octokit/plugin-paginate-rest": "^11.6.0", "@octokit/plugin-paginate-rest": "^11.6.0",
"@octokit/plugin-rest-endpoint-methods": "^13.5.0", "@octokit/plugin-rest-endpoint-methods": "^13.5.0",
"@octokit/plugin-retry": "^7.2.1",
"@octokit/plugin-throttling": "^9.6.1", "@octokit/plugin-throttling": "^9.6.1",
"node-fetch-native": "^1.6.7", "node-fetch-native": "^1.6.7",
"p-limit": "^6.2.0", "p-limit": "^6.2.0",
@@ -1375,6 +1376,38 @@
"@octokit/core": ">=6" "@octokit/core": ">=6"
} }
}, },
"node_modules/@octokit/plugin-retry": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.2.1.tgz",
"integrity": "sha512-wUc3gv0D6vNHpGxSaR3FlqJpTXGWgqmk607N9L3LvPL4QjaxDgX/1nY2mGpT37Khn+nlIXdljczkRnNdTTV3/A==",
"license": "MIT",
"dependencies": {
"@octokit/request-error": "^6.1.8",
"@octokit/types": "^14.0.0",
"bottleneck": "^2.15.3"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-retry/node_modules/@octokit/types": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^25.1.0"
}
},
"node_modules/@octokit/plugin-throttling": { "node_modules/@octokit/plugin-throttling": {
"version": "9.6.1", "version": "9.6.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.6.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.6.1.tgz",

View File

@@ -37,6 +37,7 @@
"@octokit/core": "^6.1.6", "@octokit/core": "^6.1.6",
"@octokit/plugin-paginate-rest": "^11.6.0", "@octokit/plugin-paginate-rest": "^11.6.0",
"@octokit/plugin-rest-endpoint-methods": "^13.5.0", "@octokit/plugin-rest-endpoint-methods": "^13.5.0",
"@octokit/plugin-retry": "^7.2.1",
"@octokit/plugin-throttling": "^9.6.1", "@octokit/plugin-throttling": "^9.6.1",
"node-fetch-native": "^1.6.7", "node-fetch-native": "^1.6.7",
"p-limit": "^6.2.0", "p-limit": "^6.2.0",

View File

@@ -1,7 +1,12 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {Inputs} from './create-pull-request' import {Inputs} from './create-pull-request'
import {Commit, GitCommandManager} from './git-command-manager' import {Commit, GitCommandManager} from './git-command-manager'
import {Octokit, OctokitOptions, throttleOptions} from './octokit-client' import {
Octokit,
OctokitOptions,
retryOptions,
throttleOptions
} from './octokit-client'
import pLimit from 'p-limit' import pLimit from 'p-limit'
import * as utils from './utils' import * as utils from './utils'
@@ -52,6 +57,7 @@ export class GitHubHelper {
options.baseUrl = 'https://api.github.com' options.baseUrl = 'https://api.github.com'
} }
options.throttle = throttleOptions options.throttle = throttleOptions
options.retry = retryOptions
this.octokit = new Octokit(options) this.octokit = new Octokit(options)
} }

View File

@@ -2,6 +2,7 @@ import * as core from '@actions/core'
import {Octokit as OctokitCore} from '@octokit/core' import {Octokit as OctokitCore} from '@octokit/core'
import {paginateRest} from '@octokit/plugin-paginate-rest' import {paginateRest} from '@octokit/plugin-paginate-rest'
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods' import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
import {retry} from '@octokit/plugin-retry'
import {throttling} from '@octokit/plugin-throttling' import {throttling} from '@octokit/plugin-throttling'
import {fetch} from 'node-fetch-native/proxy' import {fetch} from 'node-fetch-native/proxy'
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods' export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
@@ -11,6 +12,7 @@ export {OctokitOptions} from '@octokit/core/dist-types/types'
export const Octokit = OctokitCore.plugin( export const Octokit = OctokitCore.plugin(
paginateRest, paginateRest,
restEndpointMethods, restEndpointMethods,
retry,
throttling, throttling,
autoProxyAgent autoProxyAgent
) )
@@ -32,6 +34,11 @@ export const throttleOptions = {
} }
} }
export const retryOptions = {
// 429 is handled by the throttling plugin, so we exclude it from retry
doNotRetry: [400, 401, 403, 404, 410, 422, 429, 451]
}
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy // Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
function autoProxyAgent(octokit: OctokitCore) { function autoProxyAgent(octokit: OctokitCore) {
octokit.hook.before('request', options => { octokit.hook.before('request', options => {