actualbudget/actual · error · Error

Account with ID ${upgradingId} not found.

Error message

Account with ID ${upgradingId} not found.

What it means

When linking a GoCardless (Nordigen) bank account to upgrade an existing (off-budget/manual) account, Actual looks up the account row by the provided upgradingId. This error is thrown when no accounts row exists for that id, meaning the client referenced an account that no longer exists in the budget database.

Source

Thrown at packages/loot-core/src/server/accounts/app.ts:190

  upgradingId,
  offBudget = false,
  startingDate,
  startingBalance,
}: LinkAccountBaseParams & {
  requisitionId: string;
  account: SyncServerGoCardlessAccount;
}) {
  let id;
  const bank = await link.findOrCreateBank(account.institution, requisitionId);

  if (upgradingId) {
    const accRow = await db.first<db.DbAccount>(
      'SELECT * FROM accounts WHERE id = ?',
      [upgradingId],
    );

    if (!accRow) {
      throw new Error(`Account with ID ${upgradingId} not found.`);
    }

    id = accRow.id;
    await db.update('accounts', {
      id,
      account_id: account.account_id,
      bank: bank.id,
      account_sync_source: 'goCardless',
    });
  } else {
    id = uuidv4();
    await db.insertWithUUID('accounts', {
      id,
      account_id: account.account_id,
      mask: account.mask,
      name: account.name,
      official_name: account.official_name,
      bank: bank.id,

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Refresh the account list and re-run the bank-link flow, selecting the account again so a fresh id is sent.
  2. Verify the id exists: run a query 'SELECT * FROM accounts WHERE id = ?' with the id before linking.
  3. Re-sync the budget on the affected device so local state matches the server, then retry.
  4. If the account was deleted intentionally, create a new manual account and link the bank account to that instead.

Example fix

// before
await send('gocardless-link-account', {
  upgradingId: staleAccountId,
  ...rest,
});

// after
const acc = await q('SELECT id FROM accounts WHERE id = ?', [staleAccountId]);
if (!acc) {
  console.warn('Account no longer exists; linking as a new account instead');
}
await send('gocardless-link-account', { upgradingId: acc ? staleAccountId : null, ...rest });
Defensive patterns

Strategy: validation

Validate before calling

const acc = await db.first('SELECT id FROM accounts WHERE id = ?', [upgradingId]);
if (!acc) {
  throw new Error(`Refusing to link: account ${upgradingId} does not exist`);
}

Type guard

async function accountExists(id: string): Promise<boolean> {
  return !!(await db.first('SELECT id FROM accounts WHERE id = ?', [id]));
}

Try / catch

try {
  await send('gocardless-link-account', { upgradingId, ...rest });
} catch (e) {
  if (/Account with ID .* not found/.test(e.message)) {
    await refreshAccounts(); // re-run flow with fresh ids
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the 'gocardless-link-account' (linkGoCardlessAccount) handler with an upgradingId that was deleted, belongs to a different budget file, or was never persisted (stale client state).

Common situations: The user deleted the manual account in another tab/device before finishing the link flow; a stale sync left the UI showing a nonexistent account; the client sent an id from a different budget.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/a8b756d52bec5304. Report an issue: GitHub.