actualbudget/actual · error

Tried to edit unknown transaction id: ${id}

Error message

Tried to edit unknown transaction id: ${id}

What it means

replaceTransactions edits a transaction inside an in-memory transaction list by id. If no transaction in the list matches the given id, it throws this Error rather than silently failing, since continuing would corrupt split/parent bookkeeping.

Source

Thrown at packages/loot-core/src/shared/transactions.ts:199

    ) as TransactionEntity[],
  );
}

function replaceTransactions(
  transactions: readonly TransactionEntity[],
  id: string,
  func: (transaction: TransactionEntity) => TransactionEntity | null,
): {
  data: TransactionEntity[];
  newTransaction: TransactionEntity | null;
  diff: ReturnType<typeof diffItems<TransactionEntity>>;
} {
  const idx = transactions.findIndex(t => t.id === id);
  const trans = transactions[idx];
  const transactionsCopy = [...transactions];

  if (idx === -1) {
    throw new Error('Tried to edit unknown transaction id: ' + id);
  }

  if (trans.is_parent || trans.is_child) {
    const parentIndex = findParentIndex(transactions, idx);
    if (parentIndex == null) {
      logger.log('Cannot find parent index');
      return {
        data: [],
        diff: { added: [], deleted: [], updated: [] },
        newTransaction: null,
      };
    }

    const split = getSplit(transactions, parentIndex);
    let grouped = func(groupTransaction(split));
    const newSplit = ungroupTransaction(grouped);

    let diff: ReturnType<typeof diffItems<TransactionEntity>>;

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Verify the transaction still exists (fetch current transactions and check the id) before editing
  2. Refresh your transaction list so the id comes from current data
  3. Handle the error in a try/catch and skip/re-fetch for stale ids
  4. If editing programmatically, use the API's returned ids rather than hardcoded ones

Example fix

// before
await updateTransaction({ id: staleId, amount: 500 });
// after
const exists = (await aqlQuery('SELECT id FROM transactions WHERE id = ?', [staleId])).length > 0;
if (exists) await updateTransaction({ id: staleId, amount: 500 });
Defensive patterns

Strategy: try-catch

Validate before calling

function transactionExists(transactions, id) {
  return transactions.some(t => t.id === id);
}
if (!transactionExists(currentTransactions, id)) {
  await refetchTransactions(); // refresh before editing
}

Try / catch

try {
  await updateTransaction({ id, ...patch });
} catch (e) {
  if (e.message.startsWith('Tried to edit unknown transaction id')) {
    await refetchTransactions(); // stale list; reload and reapply if still relevant
  } else throw e;
}

Prevention

When it happens

Trigger: Calling updateTransaction, deleteTransaction, addSplitTransaction, splitTransaction, or replaceTransactions directly with an id that is not in the passed array — typically a stale id after the transaction was deleted, or an id from a different budget/dataset.

Common situations: UI acting on a cached transaction list after a concurrent delete; batch scripts replaying old ids; passing a child id to a function expecting it in the supplied list that no longer contains it; syncing conflicts that removed the row locally.

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/b9419d39627669af. Report an issue: GitHub.