[NoQA] fix: recover stuck isLoadingApp that blocks Inbox and Workspaces#95569
Conversation
|
@codex review |
|
🤖 Reviewed on request from Summary of the change
Why the fix is safe
Minor observations (non-blocking)Details
I did not execute the two new suites locally (sandbox restriction); CI will run them. The logic and the mutation-tested coverage described in the PR body line up with the code. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 015364b09e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (hasPendingReconnectRequest) { | ||
| return; |
There was a problem hiding this comment.
Reschedule the stranded check while a reconnect is pending
When the first 10s timer fires during a slow OpenApp/ReconnectApp, this branch returns and the effect has no dependency change to schedule another timer while isLoadingApp remains true. If that in-flight request later gets removed without applying finallyData (for example after the SequentialQueue retry/give-up or cancellation paths), the flag is still stranded and this recovery never calls openApp() until the component remounts, leaving Inbox/Workspaces blocked in the case this guard is meant to heal.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed, the check reschedules itself now while a request is pending. Added a test.
There was a problem hiding this comment.
Pull request overview
Addresses a production issue where ONYXKEYS.IS_LOADING_APP can remain stranded at true across reloads, leaving Inbox/Workspaces stuck behind skeleton loaders. The PR makes the loading flag self-healing via reconnect-family finallyData, and adds a client-side recovery in DelegateAccessHandler to re-run openApp() when the app is “loaded” but still stuck “loading”.
Changes:
- Clear
IS_LOADING_APPinfinallyDatafor the OpenApp/ReconnectApp Onyx data helper to self-heal stranded persisted state. - Add a stranded-
isLoadingApp === truerecovery effect inDelegateAccessHandlerthat re-firesopenApp()after a delay when no pending reconnect-family request exists. - Add unit/UI tests to cover the stranded-state reproduction and the recovery behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/libs/actions/App.ts |
Expands finallyData behavior for OpenApp/ReconnectApp Onyx data to clear stranded IS_LOADING_APP. |
src/DelegateAccessHandler.tsx |
Adds a timer-based recovery that reopens the app if isLoadingApp remains true without pending requests. |
tests/actions/IsLoadingAppStrandedTest.ts |
Adds regression tests reproducing the stranded state and verifying ReconnectApp heals it. |
tests/ui/DelegateAccessHandlerTest.tsx |
Adds UI-level tests ensuring the recovery re-fires openApp() only when appropriate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@NicolasBonet Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 015364b09e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Point 1 handled, the check reschedules while a request is pending. On naming, agreed, keeping it for consistency, can rename in a follow up. |
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
Reviewer Checklist
Screenshots/VideosAndroid: HybridApp2026-07-08.15.38.11.movAndroid: mWeb Chrome2026-07-08.15.31.41.moviOS: HybridApp2026-07-08.15.38.11.moviOS: mWeb Safari2026-07-08.15.31.41.movMacOS: Chrome / Safari2026-07-08.15.29.29.mov2026-07-08.15.35.17.mov |
|
Changes look good! |
Reviewer Checklist
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/NicolasBonet in version: 9.4.32-0 🚀
|
|
🤖 Help site review: no docs changes required I reviewed the changes in this PR against Expensify's help site files under Conclusion: No help site updates are needed. This is a purely internal bug fix that recovers a stranded
The fix simply makes the app self-heal from a stuck loading state that users previously could only escape by switching browsers. Help site articles document features and workflows, not internal loading-state recovery, so there is nothing in Since no changes are required, I did not create a draft docs PR. |
|
@BartekObudzinski @ZhenjaHorbach NO QA steps, OK to checkoff this one? |
|
@m-natarajan I think so we dont have reliable repro steps for this |
|
🚀 Deployed to production by https://github.com/grgia in version: 9.4.32-3 🚀
Bundle Size Analysis (Sentry): |
Explanation of Change
IS_LOADING_APPcan get permanently stranded attrue, which blocks Inbox and Workspaces behind loading skeletons forever while Search keeps working (it is a separate request). Refreshing never heals it; only switching browsers does.Root cause:
openApp()writesIS_LOADING_APP=trueoptimistically to storage immediately, but the clearingfinallyDataof WRITE commands is queued in memory (QueuedOnyxUpdates) until the SequentialQueue flushes. If the page reloads or the tab closes in that window, the clearing update is lost whiletruestays on disk. On the next bootHAS_LOADED_APP=truemakes the app run ReconnectApp only (never OpenApp), and ReconnectApp did not touch the flag, so nothing ever clears it. The existing recovery inDelegateAccessHandleronly handlesisLoadingApp === undefined, not a stucktrue.This was confirmed in production: a user's Onyx state export showed
isLoadingApp: true,hasLoadedApp: true, empty request queues and a stalelastFullReconnectTime, while server logs showed two hours of successful partial ReconnectApp calls that could not heal the flag.Two complementary changes:
getOnyxDataForOpenOrReconnectnow clearsIS_LOADING_APPinfinallyDatafor every command in the OpenApp/ReconnectApp family, not just OpenApp, so the next successful reconnect self-heals a stranded flag within minutes.DelegateAccessHandlergets a second recovery effect: ifisLoadingAppstaystruefor 10s while the app is loaded, online, and there is no OpenApp/ReconnectApp pending in the ongoing slot or persisted queue, it re-firesopenApp(). The pending-request guard prevents duplicate OpenApp calls during a legitimate in-flight load.Fixed Issues
$ #95571
PROPOSAL:
Tests
Automated unit tests covering this fix pass (101 tests across 7 related suites, including mutation validation that each fix is detected by its test).
Offline tests
N/A
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)Avatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari