mirror of
https://github.com/actualbudget/actual.git
synced 2026-07-16 15:14:02 -05:00
[GH-ISSUE #7855] [Bug]: Bank-sync reconciliation silently merges new imports into split-children of unrelated transactions when amount + ±7-day window match #80648
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @csantiviago on GitHub (May 15, 2026).
Original GitHub issue: https://github.com/actualbudget/actual/issues/7855
What happened?
(please note this was generated by Claude Code after debugging my specific issue)
A new bank-sync transaction is silently absorbed by a pre-existing split-child of an unrelated parent transaction, when the child has the same amount and a date within ±7 days, even when payees differ. The imported transaction never appears on its real date; the split it was merged into is left structurally inconsistent.
What I was trying to do: Run bank sync for an Brazilian account (Pluggy provider, self-hosted). A R$ 600,00 PIX received on 2026-05-15 from sender "Sender" was returned by Pluggy with transactionId=ffcfae43-..., date=2026-05-15, payeeName="Sender".
Expected: A new R$ 600,00 transaction added on 2026-05-15 with payee "Sender".
Actual: No new transaction. Instead, an existing split-child of an unrelated R$ 800,00 parent from 2026-05-08 (payee "Split", child notes "Reason X", imported_id IS NULL) was updated with the new transaction's imported_id and raw_synced_data — but its date, amount, payee were preserved. The imported transaction is invisible at its real date. The split remains balanced until the user attempts any partial update on the now-mismatched child (see "Additional note" below), at which point the child's amount is zeroed.
Root cause (read from @actual-app/api 26.5.0 dist/index.js):
matchTransactions (around line 111632) runs three reconciliation steps:
Step 3 falls back to any unmatched row in v_transactions with matching amount and date window. The candidate set is not filtered by parent_id IS NULL, so split-children are valid match targets. When the match is applied (around line 111546), the updates object includes imported_id, imported_payee, notes, cleared, raw_synced_data — but not amount, payee, or date (unless the per-account sync-update-dates preference is on).
How can we reproduce the issue?
- $600 with category "Reimbursements" and notes "Plan X"
- $200 with category "Reimbursements" and notes "Other"
- Neither child has an imported_id.
- imported_id set to xyz-...
- imported_payee set to "Bob"
- raw_synced_data set to Bob's full payload (which contains "date":"2026-05-08", "payeeName":"Bob")
- date, amount, payee unchanged → still 2026-05-01 / $600 / Alice
600 transaction is now invisible in the register on 2026-05-08. The split appears balanced in the UI, but its600 child silently represents Bob's transfer, not Alice's split intent.Where are you hosting Actual?
Other
What browsers are you seeing the problem on?
Firefox
Operating System
Linux
@coderabbitai[bot] commented on GitHub (May 15, 2026):
⚠️ Possible Duplicate Issue(s)
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
@MatissJanis commented on GitHub (May 15, 2026):
Root-cause analysis (automated triage)
Symptom: During bank sync, an incoming transaction is silently "matched" against a split-child of an unrelated parent transaction, so the imported transaction never appears in the register.
Likely root cause:
The fuzzy-matching SQL query in
matchTransactions(packages/loot-core/src/server/accounts/sync.ts:819–823) reads fromv_transactionswith onlydate >= ? AND date <= ? AND amount = ? AND account = ?— there is noparent_id IS NULLoris_child = 0guard. Thev_transactionsview (defined inpackages/loot-core/migrations/1608652596044_trans_views.sql:39–42) includes every row: parents, regular transactions, and split children. Step 3 of the fuzzy match (lines 880–888) then callsfuzzyDataset.find(row => !hasMatched.has(row.id))— the first unmatched row wins, regardless of whether it is a child. Once the child is selected as a match, the reconcile loop (lines 569–620) writesimported_id,imported_payee,notes,cleared, andraw_synced_dataonto the child, marking the incoming transaction as "already handled." The child'sdate,amount, andpayeeare left unchanged, so the parent split stays superficially balanced while the real bank transaction is invisible.Regression: Unable to determine
The available local git history for
sync.tsonly goes back to the Enable Banking integration commit (0fd510a). The fuzzy-matching logic and the unfilteredv_transactionsquery appear to have been present before this window; no commit in the visible history modifies thefuzzyDatasetquery to add or remove anis_childfilter. This looks like a longstanding design gap rather than a recent regression, but a fullgit logof the file on the upstream repo would be needed to confirm.Confidence: High
The code path was traced end-to-end: query → step-3 picker → reconcile writer. The only reason a split child would appear in
fuzzyDatasetis the missingis_child = 0/parent_id IS NULLpredicate, which is confirmed absent in both branches of the fuzzy-dataset query (lines 787–823).Open questions for the reporter (if any):
This is an automated root-cause analysis. It does not propose a fix. A maintainer will review and decide next steps.
Generated by Claude Code
@youngcw commented on GitHub (May 17, 2026):
Including split transaction in the bank sync matching was added on puprpose. Its useful for matching fees with transactions such as wire fees. Also some users compress credit card statements into a single split.
Do you expect to continually have some transactions that aren't included in the bank sync? I don't see this being a problem outside of the initial sync.
@csantiviago commented on GitHub (May 17, 2026):
Hi, no I don’t. Just thought was something that might happen with others but it was a specific case.
@youngcw commented on GitHub (May 17, 2026):
I think this is the first time I've seen any issue with matching splits. I guess you just happened to have something with the same amount within the matching window that wasn't already matched. Im glad you were able to track down your issue. Ill close this since the behavior is deliberate but if you have any more problems feels free to reach out.