fix(github): add a per-request timeout to all Octokit calls
Octokit v22 uses native fetch, which has no built-in timeout, so a stuck GitHub request hung until the PR-status route's 12s overall budget fired — and one slow request could consume the entire budget. Wrap fetch with an 8s AbortSignal.timeout via a shared createOctokit() factory, and route the inline Octokit instantiations through it too.
This commit is contained in:
@@ -21,6 +21,7 @@ export {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
getOctokitOrNull,
|
getOctokitOrNull,
|
||||||
|
createOctokit,
|
||||||
} from './octokit.js';
|
} from './octokit.js';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -2,6 +2,26 @@ import { Octokit } from '@octokit/rest';
|
|||||||
import { getGitHubAuth, isGhCliActive, isGhCliDisabled } from './auth.js';
|
import { getGitHubAuth, isGhCliActive, isGhCliDisabled } from './auth.js';
|
||||||
import { getGhCliToken } from './gh-cli-credential.js';
|
import { getGhCliToken } from './gh-cli-credential.js';
|
||||||
|
|
||||||
|
// Per-request timeout for every GitHub call. Octokit v22 uses native fetch,
|
||||||
|
// which has no built-in timeout — without this, a stuck connection hangs until
|
||||||
|
// some outer bound (the PR-status route's 12s overall budget) fires, and a
|
||||||
|
// single slow request can eat the whole budget. Bounding each request lets the
|
||||||
|
// caller fail fast and fall back to cached state instead.
|
||||||
|
const OCTOKIT_REQUEST_TIMEOUT_MS = 8000;
|
||||||
|
|
||||||
|
const timeoutFetch = (url, options = {}) => {
|
||||||
|
// Respect a caller-provided signal if present; otherwise attach our timeout.
|
||||||
|
if (options.signal) {
|
||||||
|
return fetch(url, options);
|
||||||
|
}
|
||||||
|
return fetch(url, { ...options, signal: AbortSignal.timeout(OCTOKIT_REQUEST_TIMEOUT_MS) });
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Create an Octokit instance with a per-request timeout applied. */
|
||||||
|
export function createOctokit(token) {
|
||||||
|
return new Octokit({ auth: token, request: { fetch: timeoutFetch } });
|
||||||
|
}
|
||||||
|
|
||||||
export function getOctokitOrNull() {
|
export function getOctokitOrNull() {
|
||||||
const auth = getGitHubAuth();
|
const auth = getGitHubAuth();
|
||||||
const ghToken = !isGhCliDisabled() ? getGhCliToken() : null;
|
const ghToken = !isGhCliDisabled() ? getGhCliToken() : null;
|
||||||
@@ -9,5 +29,5 @@ export function getOctokitOrNull() {
|
|||||||
if (!token) {
|
if (!token) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Octokit({ auth: token });
|
return createOctokit(token);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ export function registerGitHubRoutes(app) {
|
|||||||
|
|
||||||
if (ghToken !== null && !ghCliDisabled) {
|
if (ghToken !== null && !ghCliDisabled) {
|
||||||
try {
|
try {
|
||||||
const { Octokit } = await import('@octokit/rest');
|
const { createOctokit } = await import('./octokit.js');
|
||||||
ghCliUser = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
ghCliUser = await getGitHubUserSummary(createOctokit(ghToken));
|
||||||
} catch {
|
} catch {
|
||||||
ghCliUser = null;
|
ghCliUser = null;
|
||||||
}
|
}
|
||||||
@@ -246,8 +246,8 @@ export function registerGitHubRoutes(app) {
|
|||||||
return res.status(500).json({ error: 'Missing access_token from GitHub' });
|
return res.status(500).json({ error: 'Missing access_token from GitHub' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Octokit } = await import('@octokit/rest');
|
const { createOctokit } = await import('./octokit.js');
|
||||||
const octokit = new Octokit({ auth: accessToken });
|
const octokit = createOctokit(accessToken);
|
||||||
const user = await getGitHubUserSummary(octokit);
|
const user = await getGitHubUserSummary(octokit);
|
||||||
|
|
||||||
setGitHubAuth({
|
setGitHubAuth({
|
||||||
@@ -283,8 +283,8 @@ export function registerGitHubRoutes(app) {
|
|||||||
return res.status(404).json({ error: 'GitHub CLI account not found' });
|
return res.status(404).json({ error: 'GitHub CLI account not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Octokit } = await import('@octokit/rest');
|
const { createOctokit } = await import('./octokit.js');
|
||||||
const user = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
const user = await getGitHubUserSummary(createOctokit(ghToken));
|
||||||
setGhCliActive(true);
|
setGhCliActive(true);
|
||||||
const accounts = getGitHubAuthAccounts()
|
const accounts = getGitHubAuthAccounts()
|
||||||
.map((account) => ({ ...account, current: false }))
|
.map((account) => ({ ...account, current: false }))
|
||||||
@@ -319,8 +319,8 @@ export function registerGitHubRoutes(app) {
|
|||||||
let ghCliUser = null;
|
let ghCliUser = null;
|
||||||
if (ghToken) {
|
if (ghToken) {
|
||||||
try {
|
try {
|
||||||
const { Octokit } = await import('@octokit/rest');
|
const { createOctokit } = await import('./octokit.js');
|
||||||
ghCliUser = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
ghCliUser = await getGitHubUserSummary(createOctokit(ghToken));
|
||||||
accounts = accounts.concat({
|
accounts = accounts.concat({
|
||||||
id: GH_CLI_ACCOUNT_ID,
|
id: GH_CLI_ACCOUNT_ID,
|
||||||
user: ghCliUser,
|
user: ghCliUser,
|
||||||
|
|||||||
Reference in New Issue
Block a user