actualbudget/actual · error

Transaction ${id} does not belong to account ${accountId}

Error message

Transaction ${id} does not belong to account ${accountId}

What it means

moveTransaction also verifies the transaction's account field matches the accountId argument; a mismatch throws 'Transaction <id> does not belong to account <accountId>'. This prevents moving a transaction between positions of an account it does not belong to.

Source

Thrown at packages/loot-core/src/server/db/index.ts:927

 *
 * @param id - The ID of the transaction to move
 * @param accountId - The account the transaction belongs to
 * @param targetId - The ID of the transaction to place AFTER, or null to place at top
 */
export async function moveTransaction(
  id: string,
  accountId: string,
  targetId: string | null,
) {
  await batchMessages(async () => {
    const transaction = await getTransaction(id);
    if (!transaction) {
      throw new Error(`Transaction not found: ${id}`);
    }

    // Validate that the transaction belongs to the specified account
    if (transaction.account !== accountId) {
      throw new Error(
        `Transaction ${id} does not belong to account ${accountId}`,
      );
    }

    // Convert date string (YYYY-MM-DD) to integer format (YYYYMMDD) for SQL query
    const dateInt = parseInt(transaction.date.replace(/-/g, ''), 10);

    // Get transactions to reorder against.
    // If this is a child transaction, scope to siblings with the same parent_id.
    // Otherwise, get all parent transactions for the same date (excluding children).
    // Query in DESC order to match UI display order.
    const isChild = transaction.is_child && transaction.parent_id;
    const transactions = await all<{ id: string; sort_order: number }>(
      isChild
        ? `SELECT vt.id, vt.sort_order
           FROM v_transactions vt
           WHERE vt.parent_id = ?
           ORDER BY sort_order DESC, id`

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pass the account id the transaction currently belongs to (transaction.account), not the destination account
  2. Re-fetch the transaction to get its current account before calling moveTransaction
  3. If the goal is to change the transaction's account, update the transaction's account field instead of using moveTransaction
  4. Catch the error, re-sync, and retry with the corrected accountId

Example fix

// before
await aqlQuery.moveTransaction(txn.id, targetAccountId, targetId);
// after
await aqlQuery.moveTransaction(txn.id, txn.account, targetId);
Defensive patterns

Strategy: validation

Validate before calling

const txn = await aqlQuery.getTransaction(txnId);
if (txn && txn.account !== accountId) {
  throw new Error(`Transaction ${txnId} belongs to ${txn.account}, not ${accountId}`);
}

Type guard

function belongsToAccount(txn: { account: string } | null, accountId: string): txn is { account: string } {
  return txn !== null && txn.account === accountId;
}

Try / catch

try {
  await aqlQuery.moveTransaction(txnId, accountId, targetId);
} catch (e) {
  if (e.message.includes('does not belong to account')) {
    const txn = await aqlQuery.getTransaction(txnId);
    await aqlQuery.moveTransaction(txnId, txn.account, targetId);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling moveTransaction with an accountId different from transaction.account — e.g. passing the target account instead of the source account, or moving within account B a transaction that lives in account A.

Common situations: UI drag-and-drop sending the wrong account context; off-by-one after a transaction was transferred between accounts (its account field changed); batch operations iterating accounts with mismatched id variables.

Related errors


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