You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$250] Full story : Duplicate member shown in Members list with different roles/permissions #72095
Version Number: Reproducible in staging?: Needs Reproduction Reproducible in production?: Needs Reproduction If this was caught during regression testing, add the test name, ID and link from TestRail: Email or phone of affected tester (no customers): Logs:https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by:@dylanexpensify Slack conversation (hyperlinked to channel name): #Expensify Bugs
Action Performed:
Go to Workspace settings → Members
Observe the list of members
Notice that one user (same email address) appears twice with different roles (e.g., one as Admin, one as Member)
Expected Result:
Each user should appear only once in the Members list.
Actual Result:
The same user appears twice in the list. Each entry shows different permissions (e.g., Admin + Member).
Workaround:
Unknown
Platforms:
Select the officially supported platforms where the issue was reproduced:
Android: App
Android: mWeb Chrome
iOS: App
iOS: mWeb Safari
iOS: mWeb Chrome
Windows: Firefox
MacOS: Chrome / Safari
MacOS: Desktop
Platforms Tested:
On which of our officially supported platforms was this issue tested:
As a result, both the cache and the source array share the same object reference. If the original detail is mutated later, those changes also affect the cached version, leading to unintended duplication.
What changes do you think we should make in order to solve the problem?
Instead of assigning the object directly, we should store a shallow copy using {...detail} at:
It can be observed that emailToPersonalDetailsCache, allPersonalDetails, and personalDetails all share the same PersonalDetails item reference. Therefore, any modification to one collection item will affect the others.
NOTE:
- I’ve checked everything, and this is the only place where a new assignment occurs.
- We can freeze the result returned from useOnyx and Onyx.connect to prevent accidental mutations.
What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?
NA
What alternative solutions did you explore? (Optional)
NA
Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.
When members are added to a workspace through different flows (manual invitation, expense submission, workflow updates), the email keys may have different casings:
JavaScript treats these as two distinct object keys. When getMemberAccountIDsForWorkspace() iterates over these keys, it looks up personal details using getPersonalDetailByEmail(), which normalizes emails to lowercase.
What changes do you think we should make in order to solve the problem?
We can add deduplication logic to prevent the same accountID from being processed multiple times:
functiongetMemberAccountIDsForWorkspace(...){constmembers=employeeList??{};constmemberEmailsToAccountIDs: MemberEmailsToAccountIDs={};constprocessedAccountIDs=newSet<number>();Object.keys(members).forEach((email)=>{// ... existing validation logic ...constaccountID=Number(personalDetail.accountID);// Prevent duplicate entries for the same account with different email casingsif(processedAccountIDs.has(accountID)){return;}processedAccountIDs.add(accountID);memberEmailsToAccountIDs[email]=accountID;});returnmemberEmailsToAccountIDs;}
And in WorkspaceMembersPage.tsx, only set employee details for unprocessed accountIDs
What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?
We can add test
it('should deduplicate members with different email casings',()=>{constemployeeList={'user@company.com': {role: 'user',email: 'user@company.com'},'User@Company.com': {role: 'user',email: 'User@Company.com'},'admin@company.com': {role: 'admin',email: 'admin@company.com'},};constresult=getMemberAccountIDsForWorkspace(employeeList,true);constaccountIDs=Object.values(result);// Should return only 2 unique accountIDs (not 3)expect(accountIDs.length).toBe(2);expect(newSet(accountIDs).size).toBe(2);});
What alternative solutions did you explore? (Optional)
Please re-state the problem that we are trying to solve in this issue.
Duplicate members shown in WS members page after creating a money request.
What is the root cause of that problem?
If we check the onyx data, it contains the correct member list data. We get each member accountID by email from policyMemberEmailsToAccountIDs mapping. However, there is a member email that maps to the same accountID. This means there is somewhere in the code that mutates the data.
It was added to fix this bug. The bug is that, when user A request money from user B, as user B, the last message should read "You: User B owes $xx", but the actual result is that the "You:" doesn't show. But if we observe the current behavior in main, it's the opposite. User A now sees the "You:" instead of User B.
We previously get the last actor account ID from the report (old code)
(the comment mention in case it's empty string, but the code doesn't check that at all and I don't see it's happening, if that happens, then it's definitely a BE issue)
With this change, when User A requests from User B,
User A will see: "User B owes $xx"
User B will see: "You: User B owes $xx"
If that's not expected, we should change the logic inside getReportActionActorAccountID instead of mutating the personal detail object.
Activity
melvin-bot commentedon Oct 8, 2025
Triggered auto assignment to @kadiealexander (
Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.MelvinBot commentedon Oct 8, 2025
This has been labelled "Needs Reproduction". Follow the steps here: https://stackoverflowteams.com/c/expensify/questions/16989
truph01 commentedon Oct 9, 2025
I can reproduce this issue:
Screen.Recording.2025-10-10.at.02.35.39.mov
paulnjs commentedon Oct 10, 2025
Re-post proposal
Proposal
Please re-state the problem that we are trying to solve in this issue.
What is the root cause of that problem?
The logic used to retrieve and display workspace members originates from this line:
App/src/pages/workspace/WorkspaceMembersPage.tsx
Line 410 in 332616e
The
policyMemberEmailsToAccountIDsused there is derived from:App/src/libs/PolicyUtils.ts
Line 371 in 332616e
That function calls
getPersonalDetailByEmail, found here:App/src/libs/PersonalDetailsUtils.ts
Lines 140 to 142 in 332616e
getPersonalDetailByEmailrelies on theemailToPersonalDetailsCache:App/src/libs/PersonalDetailsUtils.ts
Lines 21 to 36 in 332616e
Within that implementation, the cache stores the original reference to the
detailobject directly:App/src/libs/PersonalDetailsUtils.ts
Line 31 in 332616e
As a result, both the cache and the source array share the same object reference. If the original
detailis mutated later, those changes also affect the cached version, leading to unintended duplication.What changes do you think we should make in order to solve the problem?
Instead of assigning the object directly, we should store a shallow copy using
{...detail}at:App/src/libs/PersonalDetailsUtils.ts
Line 31 in 332616e
Alternatively, we could perform a deep clone of
valusing_.cloneDeep()at:App/src/libs/PersonalDetailsUtils.ts
Line 26 in 332616e
This ensures that subsequent mutations to the original object do not affect the cached data.
What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?
None.
What alternative solutions did you explore? (Optional)
N/A
dmkt9 commentedon Oct 10, 2025
Proposal
Please re-state the problem that we are trying to solve in this issue.
Duplicate members list shown after submitter create expenses in workspace chat
What is the root cause of that problem?
The
accountIDis retrieved here viapolicyMemberEmailsToAccountIDsto generate the displaydata:App/src/pages/workspace/WorkspaceMembersPage.tsx
Lines 406 to 410 in ac3686a
policyMemberEmailsToAccountIDsis obtained here from thegetMemberAccountIDsForWorkspacefunction:App/src/pages/workspace/WorkspaceMembersPage.tsx
Lines 85 to 97 in ac3686a
Inside the
getMemberAccountIDsForWorkspacefunction, the result is returned based on thegetPersonalDetailByEmailfunction:App/src/libs/PolicyUtils.ts
Lines 387 to 391 in ac3686a
App/src/libs/PersonalDetailsUtils.ts
Lines 140 to 142 in ac3686a
It can be observed that
emailToPersonalDetailsCache,allPersonalDetails, andpersonalDetailsall share the same PersonalDetails item reference. Therefore, any modification to one collection item will affect the others.App/src/libs/PersonalDetailsUtils.ts
Lines 27 to 34 in ac3686a
At this point, a new
accountIDwas assigned to aPersonalDetailsitem, causing it to duplicate theaccountIDof another item:App/src/libs/SidebarUtils.ts
Lines 714 to 729 in ac3686a
What changes do you think we should make in order to solve the problem?
Instead of returning a reference to the item, we should return a cloned object or only extract the necessary properties:
App/src/libs/SidebarUtils.ts
Line 714 in ac3686a
NOTE:
- I’ve checked everything, and this is the only place where a new assignment occurs.
- We can freeze the result returned from
useOnyxandOnyx.connectto prevent accidental mutations.What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?
NA
What alternative solutions did you explore? (Optional)
NA
Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.
mukhrr commentedon Oct 10, 2025
Proposal
Please re-state the problem that we are trying to solve in this issue.
After an employee submits expenses in a workspace chat, duplicate member entries appear on the workspace members page.
What is the root cause of that problem?
The root cause is case-sensitive email keys in the
policy.employeeListobject combined with email normalization inconsistencies.App/src/components/WorkspaceMembersSelectionList.tsx
Lines 51 to 62 in ac3686a
When members are added to a workspace through different flows (manual invitation, expense submission, workflow updates), the email keys may have different casings:
JavaScript treats these as two distinct object keys. When
getMemberAccountIDsForWorkspace()iterates over these keys, it looks up personal details usinggetPersonalDetailByEmail(), which normalizes emails to lowercase.App/src/libs/PolicyUtils.ts
Lines 374 to 391 in ac3686a
App/src/libs/PersonalDetailsUtils.ts
Lines 140 to 142 in ac3686a
Both variations map to the same
accountID, resulting in duplicate entries for the same user in the members list.App/src/pages/workspace/WorkspaceMembersPage.tsx
Lines 85 to 95 in ac3686a
What changes do you think we should make in order to solve the problem?
We can add deduplication logic to prevent the same
accountIDfrom being processed multiple times:And in WorkspaceMembersPage.tsx, only set employee details for unprocessed accountIDs
What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?
We can add test
What alternative solutions did you explore? (Optional)
bernhardoj commentedon Oct 10, 2025
Proposal
Please re-state the problem that we are trying to solve in this issue.
Duplicate members shown in WS members page after creating a money request.
What is the root cause of that problem?
If we check the onyx data, it contains the correct member list data. We get each member
accountIDbyemailfrompolicyMemberEmailsToAccountIDsmapping. However, there is a memberemailthat maps to the sameaccountID. This means there is somewhere in the code that mutates the data.App/src/pages/workspace/WorkspaceMembersPage.tsx
Lines 409 to 415 in ac3686a
policyMemberEmailsToAccountIDsis built fromgetMemberAccountIDsForWorkspaceand the personal detail is fromgetPersonalDetailByEmail.App/src/pages/workspace/WorkspaceMembersPage.tsx
Lines 85 to 86 in ac3686a
App/src/libs/PolicyUtils.ts
Lines 387 to 391 in ac3686a
App/src/libs/PersonalDetailsUtils.ts
Lines 140 to 142 in ac3686a
We build
emailToPersonalDetailsCachefrom the personal details onyx data.App/src/libs/PersonalDetailsUtils.ts
Lines 23 to 33 in ac3686a
And the mutation happens here.
App/src/libs/SidebarUtils.ts
Lines 727 to 729 in ac3686a
It was added to fix this bug. The bug is that, when user A request money from user B, as user B, the last message should read "You: User B owes $xx", but the actual result is that the "You:" doesn't show. But if we observe the current behavior in
main, it's the opposite. User A now sees the "You:" instead of User B.We previously get the last actor account ID from the report (old code)
App/src/libs/SidebarUtils.ts
Lines 506 to 516 in 0754949
but now we get it from
getReportActionActorAccountID.App/src/libs/SidebarUtils.ts
Line 710 in ac3686a
App/src/libs/ReportUtils.ts
Lines 10267 to 10277 in ac3686a
What changes do you think we should make in order to solve the problem?
First of all, we need to remove this workaround.
App/src/libs/SidebarUtils.ts
Lines 726 to 729 in ac3686a
(the comment mention in case it's empty string, but the code doesn't check that at all and I don't see it's happening, if that happens, then it's definitely a BE issue)
With this change, when User A requests from User B,
User A will see: "User B owes $xx"
User B will see: "You: User B owes $xx"
If that's not expected, we should change the logic inside
getReportActionActorAccountIDinstead of mutating the personal detail object.46 remaining items
truph01 commentedon Oct 27, 2025
@cead22 Could you handle the payment for this issue? Both C and C+ were assigned, which aligns with our guidelines.
cead22 commentedon Oct 27, 2025
Can you remind me how we handle payments for these cases?
truph01 commentedon Oct 27, 2025
@cead22 Here is the reference.
cead22 commentedon Oct 27, 2025
Thank you! @kadiealexander can you handle the payments for this please?
mallenexpensify commentedon Oct 27, 2025
Payment Summary
Contributor: @bernhardoj due $125 via NewDot
Contributor+: @truph01 due $125 via Upwork (offer)
Thx
bernhardoj commentedon Oct 28, 2025
Requested in ND.
truph01 commentedon Oct 29, 2025
@mallenexpensify It'd be via Upwork for me
quinthar commentedon Nov 2, 2025
Approved $125 to @bernhardoj
kadiealexander commentedon Nov 7, 2025
@truph01 paid via Upwork.