{"record":{"id":"7530704ecec5fec8","repo":"actualbudget/actual","slug":"aborted","errorCode":"aborted","errorMessage":"aborted","messagePattern":"aborted","errorType":"error_code","errorClass":"PostError","httpStatus":null,"severity":"info","filePath":"packages/loot-core/src/server/post.ts","lineNumber":86,"sourceCode":"  try {\n    const signal = timeout != null || externalSignal ? controller.signal : null;\n    res = await fetch(url, {\n      method: 'POST',\n      body: JSON.stringify(data),\n      signal,\n      headers: {\n        ...headers,\n        'Content-Type': 'application/json',\n      },\n    });\n    text = await res.text();\n  } catch (err) {\n    if (\n      err instanceof Error &&\n      err.name === 'AbortError' &&\n      externalSignal?.aborted\n    ) {\n      throw new PostError('aborted');\n    }\n    throw new PostError('network-failure', undefined, { cause: err });\n  } finally {\n    if (timeoutId != null) clearTimeout(timeoutId);\n    externalSignal?.removeEventListener('abort', onExternalAbort);\n  }\n\n  throwIfNot200(res, text);\n\n  let responseData;\n\n  try {\n    responseData = JSON.parse(text);\n  } catch {\n    // Something seriously went wrong. TODO handle errors\n    throw new PostError('parse-json', { meta: text });\n  }\n","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/post.ts#L68-L104","documentation":"PostError with sentinel message 'aborted' thrown by post when the caller-supplied AbortSignal fired and fetch rejected with an AbortError — i.e. the request was cancelled by the caller, not a timeout or network fault.","triggerScenarios":"Passing an externalSignal to post() and aborting it (via AbortController.abort()) while the request is in flight; component unmount cancelling a sync request.","commonSituations":"UI cancelling a slow sync when navigating away, user pressing a cancel button, React effects aborting fetches on cleanup, duplicate-request suppression aborting the older call.","solutions":["Catch PostError 'aborted' and treat it as an expected cancellation, not a failure","Only abort the signal when cancellation is truly intended","If aborts are accidental, stop aborting controllers on unmount/hide","Re-issue the request if the operation must complete"],"exampleFix":"// before\ntry { await post(url, data, { signal: controller.signal }); } catch (e) { throw e; }\n// after\ntry { await post(url, data, { signal: controller.signal }); }\ncatch (e) {\n  if (e instanceof PostError && e.message === 'aborted') return; // cancelled by us\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// only wire an AbortController if cancellation is genuinely needed\nconst controller = shouldAllowCancel ? new AbortController() : null;","typeGuard":"function isAbortError(e: unknown): e is PostError {\n  return e instanceof PostError && e.message === 'aborted';\n}","tryCatchPattern":"try {\n  await post(url, data, { signal: controller?.signal });\n} catch (e) {\n  if (isAbortError(e)) return; // expected cancellation — ignore\n  throw e;\n}","preventionTips":["Treat 'aborted' as success-path cancellation, never surface it as a user-facing error","Only abort requests that are safe to abandon","Track controller lifetime with the UI element that owns the request","Don't reuse an already-aborted AbortController for new requests"],"tags":["network","abort","cancellation"],"backgroundTag":"request-aborted","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}