{"record":{"id":"0a0bf858d540643a","repo":"TryGhost/Ghost","slug":"request-timed-out-please-try-again","errorCode":null,"errorMessage":"Request timed out, please try again.","messagePattern":"Request timed out, please try again\\.","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"apps/admin-x-framework/src/utils/api/fetch-api.ts","lineNumber":240,"sourceCode":"                    // Awaited so response errors reject inside the try/catch\n                    return await handleResponse(response) as ResponseData;\n                } catch (error) {\n                    retryingMs = Date.now() - startTime;\n\n                    if (retry && (import.meta.env.MODE !== 'development' && retryableErrors.some(errorClass => error instanceof errorClass) && retryingMs <= maxRetryingMs)) {\n                        await new Promise((resolve) => {\n                            setTimeout(resolve, retryPeriods[attempts] || retryPeriods[retryPeriods.length - 1]);\n                        });\n                        attempts += 1;\n                        continue;\n                    }\n\n                    if (attempts !== 0 && sentryDSN) {\n                        Sentry.captureMessage('Request failed after multiple attempts', {extra: getErrorData()});\n                    }\n\n                    if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {\n                        throw new TimeoutError();\n                    }\n\n                    if (error instanceof UnauthorizedError && isSessionExpiry(endpoint)) {\n                        redirectOnSessionExpiry();\n                        throw new SessionExpiredError(error.response!, error.data, {cause: error});\n                    }\n\n                    let newError = error;\n\n                    if (!(error instanceof APIError)) {\n                        newError = new ServerUnreachableError({cause: error});\n                    }\n\n                    throw newError;\n                };\n            }\n        } finally {\n            clearTimeout(timeoutHandle);","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin-x-framework/src/utils/api/fetch-api.ts#L222-L258","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\nconst {data, error} = useBrowsePosts({timeout: 5000});\n\n// after\nconst {data, error} = useBrowsePosts({timeout: 30_000});\n// and in an effect/handler:\nif (error instanceof TimeoutError) { /* retry once */ }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"import {TimeoutError} from '@tryghost/admin-x-framework/utils/errors';\nfunction isTimeoutError(e: unknown): e is TimeoutError {\n    return e instanceof TimeoutError;\n}","tryCatchPattern":"import {TimeoutError} from '@tryghost/admin-x-framework/utils/errors';\ntry {\n    await someApiCall({timeout: 30_000});\n} catch (e) {\n    if (e instanceof TimeoutError) {\n        // optional single retry — the library does not retry aborts\n        await someApiCall({timeout: 30_000});\n    } else {\n        throw e;\n    }\n}","preventionTips":["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."],"tags":["network","timeout","abort","retry","fetch-api"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}