What happened?
Client version: v26.8.1
Server version: v26.8.1
Provider: Enable Banking (production app)
Bank: Trade Republic (ES)
Every transaction imported from Trade Republic via Enable Banking is created exactly twice, on the very first import into an empty account. It is not a re-import issue: a brand new transaction is duplicated the first time it is ever seen.
Root cause looks like the pagination loop in the Enable Banking service. In getAllTransactions, the page is pushed into the result array before the repeated-continuation-key check:
do {
const result = await enableBankingService.getTransactions(
accountUid, dateFrom, dateTo, continuationKey, psuHeaders);
allTransactions.push(...result.transactions); // pushed first
if (result.continuation_key && result.continuation_key === continuationKey) break; // checked after
continuationKey = result.continuation_key;
iteration++;
} while (continuationKey && iteration < maxIterations);
If the ASPSP returns a continuation_key on the last page and then answers that key with the same page and the same key (which is what Trade Republic appears to do), the loop pushes the same transactions a second time and only then breaks. That yields exactly two copies — never three — which matches what we observe.
Trade Republic makes this visible because it returns transactions with no identifying fields at all: entry_reference, transaction_id, remittance information and creditor/debtor names are all empty. The server maps
const transactionId = tx.entry_reference || tx.transaction_id || "";
so imported_id ends up empty, dedup on import cannot fire, and fuzzy matching has nothing to match on either (no payee, no notes — only date and amount). Banks that return a usable entry_reference would silently swallow the second copy, which may be why this has gone unnoticed.
Evidence
Emptied the account completely, then ran a single bank sync:
| amount |
imported_id |
imported_payee |
cleared |
| -10.00 |
"" |
"" |
true |
| -10.00 |
"" |
"" |
true |
| -201.00 |
"" |
"" |
true |
| 250.00 |
"" |
"" |
true |
The -10.00 is a transfer made minutes earlier and never touched in Actual — duplicated on its first ever import. Both copies are booked (cleared: true), so this is not the pending/booked overlap; unchecking Import pending transactions and Reimport deleted transactions makes no difference.
A second account at another bank (CaixaBank/imagin) on the same Enable Banking application imports correctly, with populated imported_id and payee, and is not paginated.
How can we reproduce the issue?
- Link a Trade Republic account with Enable Banking.
- Delete all transactions from the account so it is empty.
- Run Bank Sync once.
- Every transaction is present twice.
Suggested fix
Check the repeated continuation key before appending, e.g.
if (result.continuation_key && result.continuation_key === continuationKey) break;
allTransactions.push(...result.transactions);
or track seen continuation keys in a Set and stop when one repeats. A defensive de-duplication of the accumulated page contents would also help for ASPSPs that overlap pages.
Where are you hosting Actual?
Docker
What browsers are you seeing the problem on?
Firefox, Safari (iOS)
Operating System
Linux
What happened?
Every transaction imported from Trade Republic via Enable Banking is created exactly twice, on the very first import into an empty account. It is not a re-import issue: a brand new transaction is duplicated the first time it is ever seen.
Root cause looks like the pagination loop in the Enable Banking service. In
getAllTransactions, the page is pushed into the result array before the repeated-continuation-key check:If the ASPSP returns a
continuation_keyon the last page and then answers that key with the same page and the same key (which is what Trade Republic appears to do), the loop pushes the same transactions a second time and only then breaks. That yields exactly two copies — never three — which matches what we observe.Trade Republic makes this visible because it returns transactions with no identifying fields at all:
entry_reference,transaction_id, remittance information and creditor/debtor names are all empty. The server mapsso
imported_idends up empty, dedup on import cannot fire, and fuzzy matching has nothing to match on either (no payee, no notes — only date and amount). Banks that return a usableentry_referencewould silently swallow the second copy, which may be why this has gone unnoticed.Evidence
Emptied the account completely, then ran a single bank sync:
""""""""""""""""The
-10.00is a transfer made minutes earlier and never touched in Actual — duplicated on its first ever import. Both copies are booked (cleared: true), so this is not the pending/booked overlap; unchecking Import pending transactions and Reimport deleted transactions makes no difference.A second account at another bank (CaixaBank/imagin) on the same Enable Banking application imports correctly, with populated
imported_idand payee, and is not paginated.How can we reproduce the issue?
Suggested fix
Check the repeated continuation key before appending, e.g.
or track seen continuation keys in a
Setand stop when one repeats. A defensive de-duplication of the accumulated page contents would also help for ASPSPs that overlap pages.Where are you hosting Actual?
Docker
What browsers are you seeing the problem on?
Firefox, Safari (iOS)
Operating System
Linux