mirror of
https://github.com/actualbudget/actual.git
synced 2026-07-16 23:26:48 -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 #92844
Closed
opened 2026-05-26 04:47:14 -05:00 by GiteaMirror
·
5 comments
No Branch/Tag Specified
master
sqlparam-nullable-query-params
cross-version-sync-stage2
claude/transaction-column-manager-cp0u0w
claude/verify-migration-guardrails-i82c8m
dependabot/npm_and_yarn/npm_and_yarn-cd62b4b37a
tabedzki/feat/replacing-month-picker-with-day-picker
delay-initial-sync-until-loaded
jfdoming/mobile-app-build
cross-version-sync-compat
jfdoming/api-tokens-part-1
jfdoming/api-tokens-part-2
jfdoming/api-tokens-part-3
jfdoming/mobile-app-updates
claude/budget-version-migration-compat-151jlr
claude/worker-error-serialization-9iqtl7
claude/react-table-library-research-afx83z
claude/issue-8401-regression-test-9nw9l3
jfdoming/mobile-app-foundation-part-1
jfdoming/mobile-app-scaffold
release
ai-fix-cleanup-def-8290
claude/relaxed-shannon-jusywa
claude/fervent-keller-s6f2fq
claude/determined-feynman-st1y9z
claude/wizardly-einstein-vi2wce
claude/sweet-albattani-7nseio
feat/bank-sync-per-budget-file
claude/affectionate-cannon-y0kc82
claude/dependabot-findings-m9pgmb
claude/mac-app-ios-publish-zjs1lz
claude/vigilant-tesla-aiiyos
depot-migrate-secrets-1781296330308
claude/quirky-cerf-v5qck7
claude/animation-removal-alternative-2etfh1
claude/netlify-preview-comments-consolidate-9XPYa
osc/7391-error-boundaries-reports
claude/pensive-dijkstra-7DYui
claude/server-app-version-mismatch-HTCGv
claude/pr-8027-review-feedback-mELuO
claude/zealous-wozniak-e5pO0
claude/intelligent-heisenberg-BSHJq
claude/translation-download-refactor-jFRsO
claude/mobile-performance-ideas-G9PAO
claude/github-issue-7988-5A5ly
blacksmith-migration-ff70d2d
claude/great-mayer-KRvOk
claude/react-native-ios-compilation-fcJh5
claude/serene-goldberg-58xv0
claude/kind-euler-LVG3K
release-automation/edge-nightly
7710-bundle-migrations
claude/fix-docker-build-error-WMfed
claude/llm-compatible-release-notes-t20Nr
7710-tests-tdd
cursor/transaction-table-rewrite-f077
claude/plan-ci-secure-context-OtEe1
claude/fix-issue-7667-DPXi3
cursor/resolve-pr-7449-ee11
claude/fix-typescript-build-error-JPtZ5
copilot/add-repository-configs-to-packages
worktree-compressed-drifting-ritchie
claude/api-consumer-verification-kfz1K
pr-7454
claude/fix-issue-7410-LLLQ4
revert-7281-generate-icons
react-query-useSchedules
copilot/sub-pr-6140
package-upgrades
v26.7.0
v26.6.0
v26.5.2
v26.5.1
v26.5.0
v26.4.0
v26.3.0
v26.2.1
v26.2.0
v26.1.0
v25.12.0
v25.11.0
v25.10.0
v25.9.0
v25.8.0
v25.7.1
v25.7.0
v25.6.1
v25.6.0
v25.5.0
v25.4.0
v25.3.1
v25.3.0
v25.2.1
v25.2.0
v25.1.0
v24.12.0
v24.11.0
v24.10.1
v24.10.0
v24.9.0
v24.8.0
v24.7.0
v24.6.0
v24.5.0
v24.4.0
v24.3.0
v24.2.0
v24.1.0
v23.12.0
v23.11.0
v23.10.0
v23.9.0
v23.8.1
v23.8.0
v23.7.2
v23.7.1
v23.7.0
v23.6.0
v23.5.0
v23.4.2
v23.4.1
v23.4.0
v23.3.2
v23.3.0
v23.2.9
v23.2.5
v23.1.12
v22.12.9
Labels
Clear labels
AI generated
API
bank sync
budgeting
bug
can’t replicate
dependencies
docker
documentation
electron
experimental feature
feature
feedback
goal templates
good first issue
help wanted
importers
maintenance
needs info
needs testing
needs triage
needs votes
openid
payees
pull-request
regression
reports
responsive
rules
schedules
server
✨ merged
split transactions
tech debt
tech-support
theme
transaction import
transaction reconciliation
transactions
translations
upstream
user interface
✅ approved
wontfix
Mirrored from GitHub Pull Request
Milestone
No items
No Milestone
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: github-starred/actual#92844
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking 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.