actualbudget/actual · error

Account mismatch: transaction belongs to account ${transacti

Error message

Account mismatch: transaction belongs to account ${transaction.account}, not ${accountId}

What it means

moveTransaction validates that the accountId you pass in matches the account the transaction actually belongs to. The sort-order recalculation it performs is scoped to a single account, so moving a transaction while claiming it belongs to a different account would corrupt sibling ordering. The library throws this error immediately when transaction.account !== accountId.

Source

Thrown at packages/loot-core/src/server/transactions/app.ts:87

async function moveTransaction({
  id,
  accountId,
  targetId,
}: {
  id: string;
  accountId: string;
  targetId: string | null;
}) {
  // Fetch the transaction to validate it exists and verify account
  const transaction = await db.getTransaction(id);
  if (!transaction) {
    throw new Error(`Transaction not found: ${id}`);
  }

  // Validate that the provided accountId matches the transaction's actual account
  // This prevents sort order calculations against the wrong account
  if (transaction.account !== accountId) {
    throw new Error(
      `Account mismatch: transaction belongs to account ${transaction.account}, not ${accountId}`,
    );
  }

  // Child transactions can be reordered within their parent's children
  // The db.moveTransaction handles the sibling-scoped reordering for children

  await db.moveTransaction(id, accountId, targetId);
  return {};
}

async function parseTransactionsFile({
  filepath,
  options,
}: {
  filepath: string;
  options: ParseFileOptions;
}) {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Re-fetch the transaction and pass its actual account as accountId when calling moveTransaction.
  2. If the intent is to move the transaction to a different account, do that first (update the transaction's account), then call moveTransaction with the new accountId.
  3. Check the client for stale state — refresh the transaction list after any account reassignment before reordering.
  4. Log both transaction.account and accountId at the call site to confirm which is wrong.

Example fix

// before
await moveTransaction({ id: txId, accountId: targetAccountId });
// after
const tx = await getTransaction(txId);
if (tx.account !== targetAccountId) {
  await updateTransaction(txId, { account: targetAccountId });
}
await moveTransaction({ id: txId, accountId: tx.account });
Defensive patterns

Strategy: validation

Validate before calling

const tx = await getTransaction(id);
if (tx.account !== accountId) {
  throw new Error(`Cannot move: transaction is in ${tx.account}, not ${accountId}`);
}

Type guard

function canMove(tx: { account: string }, accountId: string): boolean {
  return tx.account === accountId;
}

Try / catch

try {
  await moveTransaction({ id, accountId });
} catch (e) {
  if (e.message.startsWith('Account mismatch:')) {
    await refreshTransactions(); // reload stale state
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling moveTransaction with an accountId that differs from the account stored on the transaction row — e.g. a stale accountId from a previous fetch, passing a destination account instead of the transaction's current account, or an off-by-one/wrong-row selection in a drag-and-drop reorder UI.

Common situations: UI bugs where a transaction was just reassigned to another account but the client caches the old accountId; concurrent edits where one client moves a transaction between accounts while another reorders it; tests hard-coding an accountId.

Related errors


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