{"record":{"id":"15570ae7e51520a9","repo":"actualbudget/actual","slug":"timed-out","errorCode":"TIMED_OUT","errorMessage":"Request timed out","messagePattern":"Request timed out","errorType":"error_code","errorClass":"EnableBankingError","httpStatus":null,"severity":"error","filePath":"packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts","lineNumber":162,"sourceCode":"        headers[key] = value;\n      }\n    }\n  }\n\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n  const options: RequestInit = { method, headers, signal: controller.signal };\n  if (body !== undefined) {\n    options.body = JSON.stringify(body);\n  }\n\n  let response: Response;\n  try {\n    response = await fetch(url, options);\n  } catch (error) {\n    if (error instanceof Error && error.name === 'AbortError') {\n      throw new EnableBankingError(\n        'TIMED_OUT',\n        'TIMED_OUT',\n        'Request timed out',\n      );\n    }\n    throw error;\n  } finally {\n    clearTimeout(timer);\n  }\n\n  if (!response.ok) {\n    let responseBody: unknown;\n    try {\n      responseBody = await response.json();\n    } catch {\n      responseBody = await response.text().catch(() => 'unknown');\n    }\n    throw handleEnableBankingError(response.status, responseBody);","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts#L144-L180","documentation":"The Enable Banking HTTP wrapper aborts fetch requests after a timeout; when fetch rejects with an AbortError it is converted to EnableBankingError('TIMED_OUT','TIMED_OUT','Request timed out') so callers get a typed, retryable error instead of a raw abort.","triggerScenarios":"Any Enable Banking API call (validateCredentials, getApplication, getAspsps, startAuth, createSession, getSession) where the upstream Enable Banking endpoint does not respond within the configured AbortController timeout, or the connection hangs.","commonSituations":"Enable Banking API outage or degradation; network firewall/proxy blocking the sync-server's egress; very slow bank backend behind Enable Banking; timeout configured too aggressively for the deployment's network.","solutions":["Retry the request with backoff — TIMED_OUT is transient by design","Check outbound network access from the sync-server to api.enablebanking.com (proxy/firewall/DNS)","Increase the request timeout if the deployment routinely needs longer","Check Enable Banking status or their support for ongoing incidents"],"exampleFix":"// before: assume immediate success\nconst app = await enableBankingService.getApplication();\n// after\ntry {\n  const app = await enableBankingService.getApplication();\n} catch (e) {\n  if (e instanceof EnableBankingError && e.code === 'TIMED_OUT') {\n    await delay(2000); return enableBankingService.getApplication(); // retry\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"const maxRetries = 3;\nfor (let i = 0; i < maxRetries; i++) {\n  try {\n    return await enableBankingService.getSession(sessionId);\n  } catch (e) {\n    if (e instanceof EnableBankingError && e.code === 'TIMED_OUT' && i < maxRetries - 1) {\n      await new Promise(r => setTimeout(r, 1000 * 2 ** i)); // exponential backoff\n      continue;\n    }\n    throw e;\n  }\n}","preventionTips":["Always wrap Enable Banking calls in timeout-aware retry logic","Monitor sync-server egress to api.enablebanking.com","Set a realistic timeout for your network rather than the minimum","Subscribe to Enable Banking status updates for outage awareness"],"tags":["network","timeout","enablebanking","http"],"backgroundTag":"request-timeout","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}