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
[Due for payment 2026-11-10] [$175] [Exploratory] Navigation - App keeps on loading when opening expense from email #100682
Open a magic link carrying exitTo, either by tapping an action link in an Expensify email or with: https://new.expensify.com/v/<accountID>/123456?exitTo=/r/<reportID>
Wait 30 seconds.
Expected Result:
An offline message, the sign in page, or at minimum "This is taking longer than expected..." with a Go back button after 10 seconds.
Actual Result:
A bare centered spinner on an empty screen. No header, no error, no Go back button. The app stays unusable until the network returns.
changed the title [-][Exploratory] Navigation- App keeps on loading when opening expense from email[/-][+][Exploratory] Navigation - App keeps on loading when opening expense from email[/+]on Sep 9, 2026
The expense report screen has a loading gate with no exit condition. It shows a skeleton whenever an expense report has a non-zero total but zero transactions in Onyx — with no timeout, no retry, and no error state.
The report?.total !== 0 term is not tied to any in-flight request. Once it's true, the only way out is transactions arriving, the user going offline, or the total becoming 0.
That exactly matches the repro. The WS owner already has the report row (with its total) from OpenApp, but not its transactions — those only arrive with OpenReport. Tapping the email link resumes the backgrounded app straight into /r/<reportID>. If that OpenReport never fires or its response never lands, src/pages/inbox/ReportActions.tsx:72 returns the skeleton forever. The report in this repro has two transactions, so total !== 0 is guaranteed.
What changes do you think we should make in order to solve the problem?
Make the gate request-driven, not total-driven. In shouldWaitForTransactions, only treat "no transactions yet" as loading while an OpenReport for that report is actually pending. Drop report?.total !== 0 as a standalone reason to wait, or pair it with the loading flag the way the first term already is.
Add a bounded fallback. If transactions still haven't arrived after the request settles, render the report with an empty/error state rather than an indefinite skeleton. The same gate is used by src/components/MoneyRequestReportView/MoneyRequestReportView.tsx:156-158, so both call sites are covered by fixing the util.
Regression coverage should assert that an expense report with total !== 0 and no transactions stops showing the skeleton once its OpenReport has resolved.
Investigation details, alternatives considered, and what would confirm this
Confidence: medium. The unbounded gate is confirmed in code and was byte-identical in the reported build (9.4.74-0 = 40f2e907). What is not confirmed is whether OpenReport fails to fire on the warm deep-link path or fires and returns without transactions.
What would settle it: check whether an OpenReport request for that reportID was sent from the affected account right after the tap.
No request → the warm deep link never reached the fetch effect, and the navigation path is also at fault.
Request returned 200 → the response carried no transactions (or Onyx never merged them), and the gate above is the sole latch.
There is also a [OpenReportStall] alert at src/pages/inbox/ReportFetchHandler.tsx:501-505 that fires after 30s when the loading flag never clears. Note it will not fire for this case if OpenReport succeeded — its absence is itself a signal.
Alternatives ruled out:
The /transition handoff (the cause of the already-fixed web bug). Dead on Android: android/app/src/main/AndroidManifest.xml:72-93 claims only new.expensify.com and 21 path prefixes — /transition is not among them, and www.expensify.com is not claimed at all. Android hands both to the browser. getCurrentUrl() is also hardcoded to '' on native (src/libs/Navigation/currentUrl/index.native.ts:3), so the exitTo machinery is a no-op there regardless.
isInPreloadedTab blocking every fetchReport() call. It is a real hazard on current main, but it did not exist in 9.4.74 — useIsInPreloadedTab.ts landed with perf: preload the Reports tab so the first Inbox open runs the warm path, merged 12 days after this was reported. Verified with git ls-tree 40f2e9076a -- src/hooks/useIsInPreloadedTab.ts (empty).
Separate defect found along the way (not this bug, worth its own issue):setInitialURL in src/components/InitialURLContextProvider.tsx:40-47 has no external callers, so initialURL is a mount-time-only snapshot that is never refreshed for a warm onNewIntent link. Combined with src/libs/SessionUtils.ts:11-41, where isLoggingInAsNewUser(undefined, email) returns true, a stale snapshot can sign a valid session out. The symptom there is a sign-out, not a hang, so it isn't this issue.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/100682) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Activity
applause-bot commentedon Sep 9, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/b238bf30ac9b96db288b490494d0973d6cd7880c0c3924630f7a83f345eada0d
[-][Exploratory] Navigation- App keeps on loading when opening expense from email[/-][+][Exploratory] Navigation - App keeps on loading when opening expense from email[/+]MelvinBot commentedon Sep 24, 2026
Proposal
What is the root cause of that problem?
The expense report screen has a loading gate with no exit condition. It shows a skeleton whenever an expense report has a non-zero total but zero transactions in Onyx — with no timeout, no retry, and no error state.
src/libs/MoneyRequestReportUtils.ts:163:The
report?.total !== 0term is not tied to any in-flight request. Once it's true, the only way out is transactions arriving, the user going offline, or the total becoming0.That exactly matches the repro. The WS owner already has the report row (with its total) from
OpenApp, but not its transactions — those only arrive withOpenReport. Tapping the email link resumes the backgrounded app straight into/r/<reportID>. If thatOpenReportnever fires or its response never lands,src/pages/inbox/ReportActions.tsx:72returns the skeleton forever. The report in this repro has two transactions, sototal !== 0is guaranteed.What changes do you think we should make in order to solve the problem?
shouldWaitForTransactions, only treat "no transactions yet" as loading while anOpenReportfor that report is actually pending. Dropreport?.total !== 0as a standalone reason to wait, or pair it with the loading flag the way the first term already is.src/components/MoneyRequestReportView/MoneyRequestReportView.tsx:156-158, so both call sites are covered by fixing the util.Regression coverage should assert that an expense report with
total !== 0and no transactions stops showing the skeleton once itsOpenReporthas resolved.Investigation details, alternatives considered, and what would confirm this
Confidence: medium. The unbounded gate is confirmed in code and was byte-identical in the reported build (
9.4.74-0=40f2e907). What is not confirmed is whetherOpenReportfails to fire on the warm deep-link path or fires and returns without transactions.What would settle it: check whether an
OpenReportrequest for that reportID was sent from the affected account right after the tap.There is also a
[OpenReportStall]alert atsrc/pages/inbox/ReportFetchHandler.tsx:501-505that fires after 30s when the loading flag never clears. Note it will not fire for this case ifOpenReportsucceeded — its absence is itself a signal.Alternatives ruled out:
/transitionhandoff (the cause of the already-fixed web bug). Dead on Android:android/app/src/main/AndroidManifest.xml:72-93claims onlynew.expensify.comand 21 path prefixes —/transitionis not among them, andwww.expensify.comis not claimed at all. Android hands both to the browser.getCurrentUrl()is also hardcoded to''on native (src/libs/Navigation/currentUrl/index.native.ts:3), so theexitTomachinery is a no-op there regardless.isInPreloadedTabblocking everyfetchReport()call. It is a real hazard on currentmain, but it did not exist in9.4.74—useIsInPreloadedTab.tslanded with perf: preload the Reports tab so the first Inbox open runs the warm path, merged 12 days after this was reported. Verified withgit ls-tree 40f2e9076a -- src/hooks/useIsInPreloadedTab.ts(empty).Separate defect found along the way (not this bug, worth its own issue):
setInitialURLinsrc/components/InitialURLContextProvider.tsx:40-47has no external callers, soinitialURLis a mount-time-only snapshot that is never refreshed for a warmonNewIntentlink. Combined withsrc/libs/SessionUtils.ts:11-41, whereisLoggingInAsNewUser(undefined, email)returnstrue, a stale snapshot can sign a valid session out. The symptom there is a sign-out, not a hang, so it isn't this issue.Possibly related issues:
/transitionrace), fixed in9.4.78-3. Not a duplicate.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/100682)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Sep 24, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @mkhutornyi (
External)melvin-bot commentedon Sep 24, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
50 remaining items
melvin-bot commentedon Nov 9, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Nov 9, 2026
Payment Summary
Resolving PRs:
Show the offline message instead of a bare spinner on the magic link page #102261
Reviewer: @mkhutornyi owed $175 via NewDot
BugZero Checklist (@mallenexpensify)
melvin-bot commentedon Nov 12, 2026
@mallenexpensify Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
mallenexpensify commentedon Nov 13, 2026
Payment Summary
Contributor+: @mkhutornyi due $175 via NewDot