Skip to content

[Due for payment 2026-10-22] [$175] Web - Profile - The error doesn't match the description in the test case. #102917

Closed

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


Version Number: v9.5.1-5
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/folder/52723565/detail-test-case/41236654
Email or phone of affected tester (no customers): vupham12990+2@gmail.com
Issue reported by: Applause Internal Team
Bug source: Regression TC Execution
Device used: Desktop/Windows 10
App Component: Other

Action Performed:

Preconditions: You have an email A that is already registered with an Expensify account and an email B that is registered with an Expensify account.

  1. Go to https://staging.new.expensify.com/
  2. Log in with the email A
  3. Navigate to account settings > Profile
  4. Click on "Contact Method"
  5. Verify the main contact method is the user email
  6. Click on "New Contact method"
  7. Verify the user is requested to add the magic code sent to the main account
  8. Navigate to the email inbox
  9. Enter the magic code sent to the main account and click "Verify"
  10. Enter the email B, then click the 'Add' button

Expected Result:

The error should match the description in the test case.

Actual Result:

After step 10, the user sees the following error: “Failed to add this contact method. Please reach out to Concierge for help” instead of the error required by the test case: “Existing accounts cannot be added as Contact Methods…”

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome Safari

Screenshots/Videos

Bug7275186_1790944839551.bandicam_2026-10-02_19-25-53-315.mp4
Image

View all open jobs on GitHub

Applause Internal Information
Exported by: Julia Ponikarchuk
Bug ID: 7275186

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Oct 2, 2026
  2. MelvinBot commented on Oct 2, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    The App never contains the "Existing accounts cannot be added as Contact Methods…" text. The server sends it in the AddNewContactMethod response's Onyx data. The App shows it only if it lands in the Onyx key the page reads.

    Migrate loginList to logins Onyx key with expensifyLoginsSelector (merged 2026-06-09) moved the contact method page from loginList to logins (1_<email> entries). The page now reads only logins (DynamicNewContactMethodPage.tsx:40-58). Earlier, the server put this error into loginList[email].errorFields.addedLogin. If it still writes the error only there, nothing reads it. The App then shows its own failureData fallback, "Failed to add this contact method. Please reach out to Concierge for help." (User.ts:512).

    What changes do you think we should make in order to solve the problem?

    The likely fix is on the backend. It needs to write the AddNewContactMethod "existing account" error to logins under the 1_<email> key, with a microsecond error key. The App's lookup would then show the server's message over the generic fallback (ErrorUtils.ts:136-145). No App change should be needed. An internal engineer needs to confirm the server's response shape and make the backend change.

    What alternative solutions did you explore? (Optional)

    • Make the App read loginList again. This would undo the migration, so I don't recommend it.
    • Show the response.message the server returns. The App only sends that message to tracking (OnyxUpdates.ts:74-99). Showing it would mean new plumbing for one command.
    Investigation details
    • Confidence: medium. The App side is confirmed in code. I could not see the server's response: logs were unavailable, and the web test session failed to load, so I could not reproduce the error.
    • addNewContactMethod always merges the generic translated error into logins['1_<email>'].errorFields.addedLogin when the request fails (User.ts:441-522). The server's Onyx data is applied first, and the newest error key wins. So when the server writes to the same entry, its message should show.
    • Older history: iOS - Contact method - Error message truncated after adding an existing contact (v9.2.86) shows the server text displaying correctly. Fix contact method error not displayed when email exists added the current generic fallback. At that time the server's error lived in loginList.
    • Git history has never contained the string "Existing accounts", so the App did not remove it.
    • The only client-side check compares against your own logins (DynamicNewContactMethodPage.tsx:102-104). Only the server can detect another account's email.
    • Note: the help article docs/articles/new-expensify/getting-started/Troubleshoot-Adding-Your-Work-Email.md describes the generic message as current behavior. Update it if the server message is restored.
    • To confirm: check the AddNewContactMethod response for vupham12990+2@gmail.com on 2026-10-02. If the error appears under loginList and not logins, that confirms this cause.

    Next Steps for Contributor+ team:
    To accept: The fix likely needs a backend change, so an internal engineer should confirm and take this on. @MelvinBot implement [this](https://github.com/Expensify/App/issues/102917) applies only if they confirm an App change is needed.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run · no recording available

  3. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Oct 2, 2026
  4. melvin-bot commented on Oct 2, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @DylanDylann (External)

  5. changed the title [-]Web - Profile - The error doesn't match the description in the test case.[/-] [+][$175] Web - Profile - The error doesn't match the description in the test case.[/+] on Oct 2, 2026
  6. melvin-bot commented on Oct 2, 2026

    @melvin-bot
  7. laveshfonix commented on Oct 5, 2026

    @laveshfonix

    Proposal

    What is the root cause of that problem?

    The server does send its specific error, and it writes it where the page reads it. MelvinBot's proposal asks to confirm the AddNewContactMethod response, so I captured it on a local build of main (production API; this issue also reproduces on production) while adding an email that belongs to another account:

    logins → "1_<email>" → errorFields.addedLogin → { "1791195551960999": "Cannot add an existing Expensify account as a new contact method. Please merge accounts or add a copilot instead." }
    

    The App side is the problem. The request's failureData adds the App's generic fallback to the same field, keyed with the device clock (Date.now() * 1000):

    addedLogin: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('contacts.genericFailureMessages.addContactMethod'),

    The new contact method page (and the contact method details page) show the error with the largest key:

    const key = Object.keys(errorsForField).sort().reverse().at(0) ?? '';

    The generic text only comes from this failureData, so the tester's page had either no server error for that login, or a server error with a smaller key. The capture rules out the first.

    The server's key comes from the server clock, the fallback's from the device clock. In my run with a correct clock, the server stamped its error 0.36s after the tap, so its key was larger and its message showed. If the device clock is ahead of the server by more than that gap, the fallback's key is larger and the page shows "Failed to add this contact method. Please reach out to Concierge for help.", as in this issue.

    Same account, with the browser clock moved 3s ahead (const n = Date.now; Date.now = () => n() + 3000 in the console, or set the computer clock a few seconds ahead):

    Server error key Fallback key Shown
    Correct clock 1791189008511467 1791189008149000 server message
    Clock +3s 1791195551960999 1791195554681000 generic fallback
    Clock +3s, with the change below 1791195861813843 0 server message

    I can't see the tester's clock, so this is the most likely cause rather than a confirmed one. Checking the test device on https://time.is would confirm it.

    What changes do you think we should make in order to solve the problem?

    Give the generic fallback the key 0, so any error the server sends always sorts newer, whatever the device clock says. In addNewContactMethod:

    addedLogin: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('contacts.genericFailureMessages.addContactMethod', 0),

    This is the same clock-skew fix merged on Sep 25 for the bank account fallback, with the same key 0 and comment (BankAccounts.ts#L556-L557, #101909), and the same key 0 used for the payment fallback in #85154.

    Nothing else changes: with no server error, the fallback is the only entry and still shows. optimisticData clears addedLogin on every attempt, the red dot only checks that an error exists, and clearContactMethod removes the whole entry, so none of them depend on the key.

    Tests in tests/actions/UserTest.ts: (1) device clock 3s ahead + server error → the server message is returned (fails on main); (2) no server error → the generic fallback is returned.

    main, clock +3s With the change, clock +3s
    Image Image

    Note: after the fix, the page shows the server's current text ("Cannot add an existing Expensify account…"). The test case still quotes the older server text seen in #78331 ("Existing accounts cannot be added…"), so its wording needs an update too.

    What alternative solutions did you explore? (Optional)

    • Backend change (MelvinBot's proposal): the captured response shows the server already writes the error to logins['1_<email>'], so the key location isn't the cause. MelvinBot's details also assume the server's key is always the newest, which fails when the device clock is ahead.
    • Key the fallback with server-adjusted time (getServerAnchoredDBTime / NETWORK.timeSkew): the skew comes from the HTTP Date header (1-second precision) and is only measured on OpenApp/ReconnectApp/OpenReport. Its error can be bigger than the ~0.3s gap it would need to resolve, so it can't guarantee the order. 0 always works.
    • Fix it centrally in getLatestErrorField: a server error and a client fallback are both plain timestamp keys, so the helper can't tell them apart without a marker on every caller. I'd follow the per-flow precedent (Fix: Surface specific payment error from backend instead of generic fallback #85154, [Payment due @daledah] Surface the server's bank account ownership error instead of generic copy #101909) and keep this to addNewContactMethod. The delete and set-default fallbacks in User.ts could take the same 0 if the server sends its own errors there, but not validateLogin, which is read with getEarliestErrorField, so 0 would always win there.
  8. melvin-bot commented on Oct 5, 2026

    @melvin-bot

    @DylanDylann Whoops! This issue is 2 days overdue. Let's get this updated quick!

  9. DylanDylann commented on Oct 6, 2026

    @DylanDylann
    Contributor

    Not overdue. I will review EOD or tomorrow

  10. 23 remaining items

  11. DylanDylann commented on Oct 21, 2026

    @DylanDylann
    Contributor

    Contributor+ Checklist:

    • [Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.

      Link to the comment on the PR: N/A
      Link to the comment on the Issue: N/A

    • [Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.

      Link to discussion: N/A

    • [Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.

    Regression Test Proposal

    Test:

    Precondition: You have two emails that each already have an Expensify account (email A and email B).

    1. Sign in with email A.
    2. Go to Account > Profile > Contact method.
    3. Click "New contact method".
    4. Enter the magic code sent to email A and click "Verify".
    5. Enter email B and click "Add".
    6. Verify the error explains that an existing Expensify account can't be added as a contact method.

    Do we agree 👍 or 👎

  12. added
    Awaiting PaymentAuto-added when associated PR is deployed to production
    and removed on Oct 21, 2026
  13. melvin-bot commented on Oct 21, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  14. melvin-bot commented on Oct 21, 2026

    @melvin-bot

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  15. mallenexpensify commented on Oct 24, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @DylanDylann due $175 via NewDot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions