From acb4fce6990315d8e1949e151043bc0713f3219b Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Wed, 15 Jul 2026 11:36:05 -0400 Subject: [PATCH 1/2] MergeTransaction: guard against requesting merge candidates with an empty transactionID getTransactionsForMerging could fire GetTransactionsForMerging (or write a malformed MERGE_TRANSACTION Onyx key) when targetTransaction.transactionID was blank, producing backend auth errors. Return early when the transactionID is empty. --- src/libs/actions/MergeTransaction.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index d98a13c570b6..36b9f72675b8 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -181,6 +181,9 @@ function getTransactionsForMerging({ cardList?: CardList; }) { const transactionID = targetTransaction.transactionID; + if (!transactionID) { + return; + } // Collect/Control workspaces: // - Admins and approvers: The list of eligible expenses will only contain the expenses from the report that the admin/approver triggered the merge from. This is intentionally limited since they’ll only be reviewing one report at a time. From c34aa8008a34a4652326257b02f122a0c40a3699 Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Wed, 15 Jul 2026 11:36:06 -0400 Subject: [PATCH 2/2] MergeTransaction: test empty-transactionID guard in getTransactionsForMerging Add a test asserting no merge-candidate Onyx entry is written when the target transaction has an empty transactionID. --- tests/actions/MergeTransactionTest.ts | 28 ++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/actions/MergeTransactionTest.ts b/tests/actions/MergeTransactionTest.ts index fa3995d43a67..8e54af49811b 100644 --- a/tests/actions/MergeTransactionTest.ts +++ b/tests/actions/MergeTransactionTest.ts @@ -1,5 +1,5 @@ import {getReportPreviewAction} from '@libs/actions/IOU/MoneyRequestBuilder'; -import {areTransactionsEligibleForMerge, mergeTransactionRequest, setMergeTransactionKey, setupMergeTransactionData} from '@libs/actions/MergeTransaction'; +import {areTransactionsEligibleForMerge, getTransactionsForMerging, mergeTransactionRequest, setMergeTransactionKey, setupMergeTransactionData} from '@libs/actions/MergeTransaction'; import {addComment, openReport} from '@libs/actions/Report'; import {WRITE_COMMANDS} from '@libs/API/types'; import {getLoginsByAccountIDs} from '@libs/PersonalDetailsUtils'; @@ -1419,6 +1419,32 @@ describe('mergeTransactionRequest', () => { }); }); +describe('getTransactionsForMerging', () => { + beforeEach(() => { + return Onyx.clear().then(waitForBatchedUpdates); + }); + + it('should do nothing when the target transaction has no transactionID', async () => { + // Given a target transaction with an empty transactionID + const targetTransaction = {...createRandomTransaction(0), transactionID: ''} as Transaction; + + // When we request merge candidates for it (offline path, which would otherwise write eligible transactions locally) + getTransactionsForMerging({ + isOffline: true, + targetTransaction, + transactions: {}, + policy: undefined, + report: undefined, + currentUserLogin: undefined, + }); + await waitForBatchedUpdates(); + + // Then no merge transaction entry is written for the empty key + const mergeTransaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${targetTransaction.transactionID}`); + expect(mergeTransaction).toBeUndefined(); + }); +}); + describe('setupMergeTransactionData', () => { beforeEach(() => { return Onyx.clear().then(waitForBatchedUpdates);