{"record":{"id":"84c7b573f43253b7","repo":"actualbudget/actual","slug":"unknown","errorCode":"unknown","errorMessage":"res.description || res.reason || 'unknown'","messagePattern":"res\\.description \\|\\| res\\.reason \\|\\| 'unknown'","errorType":"error_code","errorClass":"PostError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/post.ts","lineNumber":165,"sourceCode":"\n  try {\n    res = JSON.parse(text);\n  } catch {\n    // Something seriously went wrong. TODO handle errors\n    throw new PostError('parse-json', { meta: text });\n  }\n\n  if (res.status !== 'ok') {\n    logger.log(\n      'API call failed: ' +\n        url +\n        '\\nData: ' +\n        JSON.stringify(data, null, 2) +\n        '\\nResponse: ' +\n        JSON.stringify(res, null, 2),\n    );\n\n    throw new PostError(res.description || res.reason || 'unknown');\n  }\n\n  return res.data;\n}\n\nexport async function patch(url, data, headers = {}, timeout = null) {\n  let text;\n  let res;\n\n  try {\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), timeout);\n    const signal = timeout ? controller.signal : null;\n    res = await fetch(url, {\n      method: 'PATCH',\n      body: JSON.stringify(data),\n      signal,\n      headers: {","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/post.ts#L147-L183","documentation":"`del` throws this PostError when the server responds 200 with JSON whose status is not 'ok'; the message is the server's `description` or `reason`, else 'unknown'. It is a server-reported application failure for the delete operation, with request data and response logged.","triggerScenarios":"del() call rejected by the server: invalid/expired user token, file ID not owned by the user, or other server-side validation on the delete endpoint responding {status: ..., description/reason}.","commonSituations":"Token expired after server maintenance; attempting to delete a file already removed server-side; permission mismatch between the signed-in user and the file's owner; 'unknown' from a server version that omits reason fields.","solutions":["Check the thrown message and the logged request/response for the server's reason.","Re-authenticate if the reason indicates an unauthorized/token problem.","Confirm the file still exists and belongs to the current user before deleting.","Check sync server logs for the failing request when the message is 'unknown'.","Align client and server versions."],"exampleFix":"// before: deleting with a token from a different user account\nawait del(server + '/delete-user-file', { token: otherUserToken, fileId });\n// after: ensure the file belongs to the signed-in user\nconst files = await getServerFiles(token); // verify ownership first\nif (files.some(f => f.fileId === fileId)) await del(server + '/delete-user-file', { token, fileId });","handlingStrategy":"try-catch","validationCode":"// check ownership before deleting\nconst files = await getServerFiles(token);\nif (!files.some(f => f.fileId === fileId)) throw new Error(`File ${fileId} not found for current user`);","typeGuard":"function isServerRejection(e: unknown): e is { reason: string } {\n  return typeof e === 'object' && e !== null && typeof (e as any).reason === 'string' && (e as any).reason !== 'ok';\n}","tryCatchPattern":"try {\n  await del(server + '/delete-user-file', { token, fileId });\n} catch (e) {\n  if (isServerRejection(e) && /token|unauthorized/i.test(e.message)) {\n    await reauthenticate();\n  } else if (isServerRejection(e) && /not-found/i.test(e.message)) {\n    // file already gone: treat as idempotent success\n  } else throw e;\n}","preventionTips":["Validate ownership/existence of server-side resources before delete.","Handle 'file-not-found' as an idempotent outcome where safe.","Refresh tokens proactively rather than reusing long-lived ones.","Capture the logged request/response on 'unknown' reasons for support requests."],"tags":["sync-server","api-response","server-error","authentication"],"backgroundTag":"server-rejected-request","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}