TryGhost/Ghost · error · TimeoutError
Request timed out, please try again.
Error message
Request timed out, please try again.
What it means
TimeoutError, thrown by the fetchApi retry loop when the underlying fetch rejects with an AbortError. The caller passes a `timeout` option; after that many ms an AbortController aborts the request (fetch-api.ts:216), fetch rejects as AbortError, and the loop re-throws it as a TimeoutError. Note: AbortError is NOT in retryableErrors ([ServerUnreachableError, MaintenanceError, TypeError]), so it is never retried — it surfaces on the first abort.
Source
Thrown at apps/admin-x-framework/src/utils/api/fetch-api.ts:240
// Awaited so response errors reject inside the try/catch
return await handleResponse(response) as ResponseData;
} catch (error) {
retryingMs = Date.now() - startTime;
if (retry && (import.meta.env.MODE !== 'development' && retryableErrors.some(errorClass => error instanceof errorClass) && retryingMs <= maxRetryingMs)) {
await new Promise((resolve) => {
setTimeout(resolve, retryPeriods[attempts] || retryPeriods[retryPeriods.length - 1]);
});
attempts += 1;
continue;
}
if (attempts !== 0 && sentryDSN) {
Sentry.captureMessage('Request failed after multiple attempts', {extra: getErrorData()});
}
if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {
throw new TimeoutError();
}
if (error instanceof UnauthorizedError && isSessionExpiry(endpoint)) {
redirectOnSessionExpiry();
throw new SessionExpiredError(error.response!, error.data, {cause: error});
}
let newError = error;
if (!(error instanceof APIError)) {
newError = new ServerUnreachableError({cause: error});
}
throw newError;
};
}
} finally {
clearTimeout(timeoutHandle);View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Increase the `timeout` option passed to the API call for endpoints known to be slow.
- Catch TimeoutError specifically and retry the operation idempotently (the library does not retry aborts).
- Investigate server-side latency (DB slow queries, long-running jobs) if timeouts are recurring on fast endpoints.
Example fix
// before
const {data, error} = useBrowsePosts({timeout: 5000});
// after
const {data, error} = useBrowsePosts({timeout: 30_000});
// and in an effect/handler:
if (error instanceof TimeoutError) { /* retry once */ } Defensive patterns
Strategy: try-catch
Type guard
import {TimeoutError} from '@tryghost/admin-x-framework/utils/errors';
function isTimeoutError(e: unknown): e is TimeoutError {
return e instanceof TimeoutError;
} Try / catch
import {TimeoutError} from '@tryghost/admin-x-framework/utils/errors';
try {
await someApiCall({timeout: 30_000});
} catch (e) {
if (e instanceof TimeoutError) {
// optional single retry — the library does not retry aborts
await someApiCall({timeout: 30_000});
} else {
throw e;
}
} Prevention
- Pick a `timeout` sized to the endpoint's worst-case latency (uploads/imports/theme activation need much more than reads).
- Don't stack manual retries on top of many parallel calls during a brownout — that amplifies server load.
- Monitor Sentry's 'Request failed after multiple attempts' and TimeoutError rates together to spot systemic slowness.
When it happens
Trigger: A useBrowse/useEdit/etc. hook (or direct fetchApi call) is invoked with a `timeout` option and the Ghost API takes longer than that to respond. The setTimeout fires controller.abort(), the in-flight fetch rejects with name === 'AbortError', and this throw executes.
Common situations: Slow server responses on large member/list exports; a stalled request during a network brownout where the server accepted the connection but never responded; a too-aggressive timeout value passed for an endpoint known to be slow (uploads, theme activation, imports).
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to fetch image: ${response.status}
- Download failed: ${response.status} ${response.statusText}
- Failed to fetch changelog: ${response.status}
- Failed to fetch site data
- Failed to update member
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/0a0bf858d540643a.
Report an issue: GitHub.