{"record":{"id":"1e9081c5ba9eb98e","repo":"actualbudget/actual","slug":"transfer-account-can-not-be-the-account-being-clos","errorCode":null,"errorMessage":"transfer account can not be the account being closed","messagePattern":"transfer account can not be the account being closed","errorType":"exception","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/accounts/app.ts","lineNumber":671,"sourceCode":"              id: row.transfer_id,\n              payee: null,\n              transfer_id: null,\n            });\n          }\n\n          void db.deleteTransaction({ id: row.id });\n        });\n\n        void db.deleteAccount({ id });\n        void db.deleteTransferPayee({ id: transferPayee.id });\n      });\n    } else {\n      if (balance !== 0 && transferAccountId == null) {\n        throw APIError('balance is non-zero: transferAccountId is required');\n      }\n\n      if (id === transferAccountId) {\n        throw APIError('transfer account can not be the account being closed');\n      }\n\n      await db.update('accounts', { id, closed: 1 });\n\n      // If there is a balance we need to transfer it to the specified\n      // account (and possibly categorize it)\n      if (balance !== 0 && transferAccountId) {\n        const transferPayee = await db.first<Pick<db.DbPayee, 'id'>>(\n          'SELECT id FROM payees WHERE transfer_acct = ?',\n          [transferAccountId],\n        );\n\n        if (!transferPayee) {\n          throw new Error(\n            `Transfer payee with account ID ${transferAccountId} not found.`,\n          );\n        }\n","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/accounts/app.ts#L653-L689","documentation":"closeAccount rejects attempts to close an account into itself: if the supplied transferAccountId equals the id of the account being closed, the transfer would be a no-op while the account's balance would silently vanish, so the server throws 'transfer account can not be the account being closed'.","triggerScenarios":"Calling closeAccount with { id: X, transferAccountId: X } for a non-zero-balance account — the destination account for the balance transfer is the same account being closed.","commonSituations":"Scripts that set transferAccountId from the same variable as the account id (copy-paste or templating bug); UI integrations that default the transfer dropdown to the closing account; batch-closing code that assigns a fallback transfer account without checking identity.","solutions":["Choose a different transferAccountId than the account being closed.","If no transfer is needed, bring the balance to zero first and close without transferAccountId.","Add a client-side check id !== transferAccountId before invoking the API."],"exampleFix":"// before\nawait api.closeAccount({ id: accId, transferAccountId: accId });\n// after\nconst target = accId === savingsId ? checkingId : savingsId;\nawait api.closeAccount({ id: accId, transferAccountId: target });","handlingStrategy":"validation","validationCode":"if (transferAccountId === accountId) {\n  throw new Error('transferAccountId must differ from the account being closed');\n}","typeGuard":"function hasValidTransferTarget(a: { id: string; transferAccountId?: string }): boolean {\n  return a.transferAccountId == null || a.transferAccountId !== a.id;\n}","tryCatchPattern":"try {\n  await api.closeAccount({ id: accountId, transferAccountId });\n} catch (e) {\n  if (e.message.includes('can not be the account being closed')) {\n    transferAccountId = await promptForDifferentTransferAccount();\n    return closeAccountSafely(accountId, transferAccountId);\n  }\n}","preventionTips":["Never default the transfer target to the account being closed in UIs or scripts.","Assert id !== transferAccountId before calling closeAccount.","In batch jobs, derive the transfer target from a mapping rather than the loop variable.","When balance is zero, omit transferAccountId entirely to sidestep the check."],"tags":["api","accounts","validation","loot-core"],"backgroundTag":"transfer-account-same-as-closing","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}