Skip to content

Android - Expense - Email not found error is shown after submitting expense to new user #100777

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: 9.4.75-0
Reproducible in staging?: Yes
Reproducible in production?: No
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #97874
Email or phone of affected tester (no customers): jsdoijnodsdisojo@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Samsung Galaxy S25 FE / Android 16
App Component: Workspace Settings

Action Performed:

  1. Launch Expensify app.
  2. Create a new account.
  3. Open FAB > Create expense > Manual.
  4. Enter email of non-existing user.
  5. Enter amount and tap Create expense.

Expected Result:

Expense will be created without error.

Actual Result:

Email not found error is shown after submitting expense to new user.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7255420_1788984166030.Screen_Recording_20260910_034627_Expensify.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Mita Rachim
Bug ID: 7255420

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 9, 2026
  2. applause-bot commented on Sep 9, 2026

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/e6e3c4a66e5f1808eef59d28beb9436d4536fadc9eff3a1a39637c0a61d201ae

  3. melvin-bot commented on Sep 9, 2026

    @melvin-bot

    You have been assigned to this deploy blocker because you recently merged this PR: #97874

  4. github-actions commented on Sep 9, 2026

    @github-actions
    Contributor

    👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  5. melvin-bot commented on Sep 9, 2026

    @melvin-bot

    @mountiny @marufsharifi you have been assigned to this deploy blocker because you reviewed the offending PR: #97874

  6. melvin-bot commented on Sep 9, 2026

    @melvin-bot

    @JakubKorytko you have been assigned to this deploy blocker because you authored the offending PR: #97874

  7. melvin-bot commented on Sep 9, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  8. MelvinBot commented on Sep 9, 2026

    @MelvinBot
    Contributor

    🔍 Investigation Summary

    Classification: Unconfirmed — App-side exposure is confirmed, but the "Email not found" string is returned by the API, so where the fix lands is not yet proven
    Causing PR: #100215 - "feat: remove isNewManualExpenseFlowEnabled beta flag" by @thelullabyy (Medium confidence)
    Related Issues: none found — no other issue or PR in this repo mentions an "Email not found" error

    Recommendation: NEEDS INVESTIGATION

    #97874 was auto-assigned only because its URL was pasted into the report's "caught during regression testing" field, and verification below shows it does not plausibly cause this; the flow the repro actually exercises was switched on for everyone by #100215, which is the best explanation for staging-yes/production-no. The exact failing API call still needs to be captured before we can say whether the fix is App-side or backend-side.

    Assigned: @thelullabyy (PR author), @ikevin127 and @puneetlath (approving reviewers) from the highest-confidence causing PR (#100215). Existing assignees left in place.
    Labels: No changes. DeployBlockerCash stays (the issue does not reproduce on production, and it is the only blocker label on the issue). DeployBlocker was never applied, so there is nothing to remove.

    📋 Detailed Analysis

    Evidence

    1. #97874 is almost certainly not the cause.
    The report's "If this was caught during regression testing, add the test name, ID and link" field contains https://github.com/Expensify/App/pull/97874. That is the QA link for the test the tester was running, not a diagnosis — and it is what Melvin keyed the auto-assignment off. On the merits, #97874 is a navigation pre-mount performance change. It does touch IOURequestStepConfirmation.tsx and useExpenseSubmission.ts, but it explicitly handles the brand-new-recipient case rather than breaking it: getReusableP2PReportID marks a recipient with no existing chat as an optimistic destination, and that flag is threaded through to the report route as isPendingCreation=true, which ReportFetchHandler uses to skip openReport for a client-generated reportID. It changes no participant field and no API payload.

    2. The repro describes the new manual expense flow, which #100215 turned on for everyone.
    The steps enter the recipient before the amount on a single confirmation screen. That is the new manual expense flow (participant picker owned by the confirmation page), not the legacy amount → participants → confirmation flow. #100215 deleted CONST.BETAS.NEW_MANUAL_EXPENSE_FLOW and every isNewManualExpenseFlowEnabled guard, including the one that previously wrapped <ParticipantPicker> inside IOURequestStepConfirmation and the one in MoneyRequestConfirmationList that used to navigate to the standalone participants step instead of opening the parent-owned picker.

    3. This explains staging-only reproduction precisely.
    Step 2 of the repro is "Create a new account." A brand-new account has no betas, so on production it still gets the legacy participants-step flow, which works. On staging the gate is gone, so the same brand-new account gets the new flow. That matches "Reproducible in staging: Yes / Reproducible in production: No" better than any other PR in this checklist.

    4. Candidates considered and ruled out.

    Verification

    • Affected area: src/pages/iou/request/step/IOURequestStepConfirmation.tsx, src/components/MoneyRequestConfirmationList.tsx, src/pages/iou/request/ParticipantSearchResults.tsx, src/libs/actions/IOU/MoneyRequestBuilder.ts.
    • feat: remove isNewManualExpenseFlowEnabled beta flag #100215 modifies the affected files: confirmed via gh pr view 100215 --json files — it edits MoneyRequestConfirmationList.tsx, IOURequestStepConfirmation.tsx, useDefaultParticipants.ts, DynamicIOURequestStepParticipants.tsx, IOURequestStartPage.tsx and ReportUtils.ts, and the recipient row in MoneyRequestConfirmationList now unconditionally calls onOpenParticipantPicker().
    • Both PRs shipped in this build: gh api repos/Expensify/App/compare/9.4.74-2-staging...9.4.75-0-staging lists both #100215 and #97874.
    • Timing: feat: remove isNewManualExpenseFlowEnabled beta flag #100215 merged 2026-09-09T16:33:46Z; the bug was filed 2026-09-09T22:24:59Z against 9.4.75-0.
    • The error copy is not ours. Email not found (and any close variant) does not exist anywhere in src/languages/. The nearest strings are memberNotFound (Member not found.), which is not what the video shows. So the App is rendering an API error message verbatim, which is why the App diff alone cannot pin the root cause.

    Root Cause

    Confirmed so far: the recipient is now chosen through the confirmation page's own ParticipantPicker rather than the standalone participants step. For an email with no Expensify account, getUserToInviteOption synthesizes an option with a generated optimistic accountID and isOptimisticAccount: true; ParticipantSearchResults.sanitizedSelectedParticipant copies only a fixed field list onto the participant, and handleParticipantsAdded then generates a fresh optimistic reportID for the brand-new P2P recipient and writes it to the draft transaction. At submit, MoneyRequestBuilder derives the payer from participant.login.

    Not yet confirmed: which of those hand-offs produces a payload the API rejects with "Email not found" — or whether the App payload is well-formed and the API is at fault. Nothing in the App diffs proves either way.

    Concrete next steps to close this out:

    1. Reproduce on staging with network logging and capture the failing command, its parameters (especially the participant's login/accountID) and the requestID from the response. That single data point decides App vs. backend and, if App, which hand-off drops the login.
    2. If the deploy must be unblocked before that: feat: remove isNewManualExpenseFlowEnabled beta flag #100215 is a mechanical beta-flag removal, so restoring the gate is a clean, low-risk revert that returns brand-new accounts to the legacy participants-step flow. Prefer that over reverting [Performance] Pre-mount new expense destinations behind confirmation RHP #97874, which is unrelated to the recipient payload.
  9. JakubKorytko commented on Sep 10, 2026

    @JakubKorytko
    Member

    I reproduced the issue reverting my PR locally, so it's super unlikely it is related. Not 100% reproducible, it depends on whether the metadata key is loaded before the chat screen mounts. Fresh account makes it near-deterministic.

    I did some testing while checking if it's my fault, posting to speed up the fix:

    1. RequestMoney succeeds. Chat report row is written to Onyx via SET.
    2. App navigates to the new chat about 400 ms later. At mount, useOnyx for reportMetadata_<id> is still loading, so reportMetadata falls back to {isOptimisticReport: false}.
    3. Guard at ReportFetchHandler.tsx:187 that skips openReport for optimistic chats fails. openReport fires with the client-generated accountID.
    4. Backend OpenReport cannot map that accountID to a login, returns "Email not found" into errorFields.createChat. E.g. requestIDs a38d3f328e83c3ad-LAX and a38d4f7b3ae9ef99-SJC in Victoria.

    The fix would likely be a few lines in ReportFetchHandler, do not call openReport while the useOnyx result status for reportMetadata_<id> is loading, or treat report.pendingFields?.createChat as optimistic since it arrives in the same SET as the report row

    @MelvinBot please do a quick sanity check and what PR likely caused it

  10. MelvinBot commented on Sep 10, 2026

    @MelvinBot
    Contributor

    Sanity check: your mechanism is right about the outcome, wrong about the trigger — and this isn't a 9.4.75 regression

    Steps 1, 3 and 4 hold. Step 2 does not — there is no loading race, and the fix you proposed wouldn't work.

    RequestMoney's successData explicitly merges isOptimisticReport: false at MoneyRequestBuilder.ts:878. Since your own step 1 is "RequestMoney succeeds", by the time you navigate the value is legitimately false. The guard at ReportFetchHandler.tsx:187 doesn't fail to read a still-loading key — it correctly stops applying. Gating on useOnyx status would therefore change nothing, and pendingFields.createChat is cleared by the same successData.

    The defaultReportMetadata pattern you spotted at ReportFetchHandler.tsx:69 is real — loading and loaded-but-empty are genuinely indistinguishable there, and worth fixing on its own merits. It just isn't what breaks this flow. Also worth noting: the report row and its metadata go out in the same Onyx.update() batch (SET at L575, metadata MERGE at L666), so they can't tear.

    What actually causes it

    openReport passes the DM's participants through ReportUtils.getOneOnOneChatParticipants, which has no optimistic-account filter. The invited email has an optimistic personal detail with a login, so it passes, and the client-generated accountID ships as accountIDList at Report/index.ts:1791. The server can't map it and returns 666 "Email not found", which the App renders verbatim — the string exists nowhere in src/languages/.

    Causing PR: #98855 ("include 1:1 DM participants in OpenReport so stale optimistic reportIDs self-heal", merged 2026-08-19, first shipped 9.4.57-0-staging). Before it, fetchReport sent no participants, so accountIDList was always empty on this path.

    Your PR is clear, and your local revert result is expected

    The line-187 guard predates you — it came from #86719 (9.3.51-0-staging). #97874 only added the isPendingCreationFromRoute and isPreMountedDraft skips at L179-L185, both of which make openReport less likely to fire. Reverting your PR leaves L187 intact, so reproducing after a revert is exactly what should happen. Your PR was auto-assigned only because its URL was in the report's "caught during regression testing" field.

    This is a duplicate — the fix is already in review

    #99276 — "Expense - New Email Not Found via Participant Option", filed 2026-08-23 against v9.4.59-0, "Reproducible in production?: Yes", currently Reviewing. Same repro, step for step.

    Fix PR: #100596 by ahmdshrif, open since 2026-09-08 — a day before this issue was filed. It derives the same mechanism independently.

    Recommendation: close this as a duplicate of #99276 and drop DeployBlockerCash. It reproduces in production at v9.4.59-0, 18 releases before 9.4.75, so it does not block this deploy. Review #100596 instead.

    Correcting my earlier comment, and what I could not verify

    My earlier comment named #100215 ("remove isNewManualExpenseFlowEnabled beta flag") as the top candidate. That was wrong. It touches confirmation-UI components and useDefaultParticipants, but no navigation timing, no ReportFetchHandler, no reportMetadata/isOptimisticReport. getOneOnOneChatParticipants was not touched anywhere in 9.4.74-2-staging...9.4.75-0-staging. The staging-only signal that pointed me there was itself wrong — #99276 records this reproducing in production.

    I've removed thelullabyy, ikevin127 and puneetlath, whom I had assigned on that refuted theory.

    Not verified: I had no VictoriaLogs access in this session, so I could not confirm your requestIDs a38d3f328e83c3ad-LAX and a38d4f7b3ae9ef99-SJC, the jsonCode, or the parameters sent. The accountIDList → 666 link above is empirically pinned in #99276's thread rather than by me. One detail in your step 4 is a contributor inference nobody has proven: that the string lands in report.errorFields.createChat specifically, rather than on a report action's errors. Not load-bearing for the duplicate verdict.


    view run · view recording

  11. JakubKorytko commented on Sep 10, 2026

    @JakubKorytko
    Member

    Ah makes sense, so @mountiny I believe we can close this one

  12. added and removed
    DeployBlockerCashThis issue or pull request should block deployment
    on Sep 10, 2026
  13. mountiny commented on Sep 10, 2026

    @mountiny
    Contributor

    Dupe #99276

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

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2Engineering

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions