actualbudget/actual · error

Transaction not found: ${id}

Error message

Transaction not found: ${id}

What it means

moveTransaction first fetches the transaction by id; if getTransaction returns nothing (missing or tombstoned row), it throws 'Transaction not found: <id>'. This is a precondition check before the move is applied, inside a batchMessages transaction so no partial writes occur.

Source

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

}

/**
 * Move a transaction to a new position within the same date.
 * Uses the same midpoint/shove algorithm as category reordering.
 *
 * @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 }>(

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Verify the transaction id exists via getTransactions/getTransaction before moving
  2. Refresh client state (re-sync) so stale ids are replaced with current ones
  3. If the transaction was deleted intentionally, drop the queued move operation instead of retrying
  4. Check that the correct id field is being passed (transaction id, not payee/schedule/category id)

Example fix

// before
await aqlQuery.moveTransaction(txnId, accountId, targetId);
// after
const txn = await aqlQuery.getTransaction(txnId);
if (txn) {
  await aqlQuery.moveTransaction(txnId, accountId, targetId);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const txn = await aqlQuery.getTransaction(txnId);
if (!txn) throw new Error(`Cannot move: transaction ${txnId} not found`);

Type guard

function isExistingTransaction(txn: unknown): txn is { id: string; account: string; date: string } {
  return !!txn && typeof (txn as any).id === 'string' && typeof (txn as any).account === 'string';
}

Try / catch

try {
  await aqlQuery.moveTransaction(txnId, accountId, targetId);
} catch (e) {
  if (e.message.startsWith('Transaction not found')) {
    await resync(); // drop stale id from queue
  } else throw e;
}

Prevention

When it happens

Trigger: Calling moveTransaction with an id that never existed, a transaction deleted on this or another device (tombstoned), a stale id from cached client state, or a malformed/truncated id string.

Common situations: Multi-device sync where one device deleted the transaction while another tries to move it; replaying queued offline operations referencing old ids; passing a payee or category id by mistake instead of the transaction id.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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