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-05-26] [Due for payment 2026-05-26] Expensify Card - Error page briefly shown when deactivate Expensify Card #90866
Version Number: 9.3.75-1 Reproducible in staging?: Yes Reproducible in production?: No If this was caught during regression testing, add the test name, ID and link from BrowserStack:#88603 Email or phone of affected tester (no customers):duytestnguyen+222@gmail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: Windows 11 Home/Chrome App Component: Workspace Settings
Action Performed:
Preconditions:
An account with Workspace, enable the Expensify card
👋 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:
Identify the pull request that introduced this issue and revert it.
Classification: Frontend bug Causing PR: #88603 - "Migrate InteractionManager - batch 1" by @mrejdak (High confidence) Related Issues: None
Recommendation: REVERT
The PR introduced a timing regression in the Expensify Card deactivation flow. The fix needs to happen in the App repo by either reverting the PR or applying a targeted fix to restore the navigation-after-animation-completes behavior.
Assigned: @mrejdak (PR author), @collectioneur, @Pujan92, @roryabraham (approving reviewers) Labels: DeployBlockerCash kept (Frontend bug in App). No label changes made.
Detailed Analysis
Evidence
PR #88603 migrated WorkspaceExpensifyCardDetailsPage.tsx away from InteractionManager and the old ConfirmModal pattern to the new useConfirmModal / showConfirmModal promise-based API.
The bug is staging-only (not reproducible in production), and PR #88603 was merged on 2026-05-14 and is included in the current 9.3.75-1 staging deploy checklist.
The issue is directly triggered by the deactivation flow change in this PR.
The diff shows the deactivation logic was rewritten to use showConfirmModal and Navigation.goBack() is now called immediately after the promise resolves
Root Cause
Old behavior (before PR #88603):
The ConfirmModal was rendered inline with shouldSetModalVisibility={false} and navigation was triggered via the onModalHide callback, which fires only after the modal's closing animation has fully completed. This guaranteed that Navigation.goBack() was never called while the modal was still animating closed.
New behavior (after PR #88603): deactivateCard() is now an async function that awaits showConfirmModal(...). When the user confirms, ConfirmModalWrapper.handleConfirm() sets isVisible = false (starting the close animation) and then onModalHide fires — which calls closeModal(), resolving the promise. At that point, the await resumes and Navigation.goBack() is called.
The issue is that the promise resolves at the start of the onModalHide callback, but the modal's closing animation may not be fully complete at that point when shouldSetModalVisibility={false} is passed. Navigating away while the modal overlay is still visible causes the screen to briefly render an error page before the modal finishes closing.
The original code explicitly used onModalHide to defer navigation until after the animation. The new code loses this guarantee because Navigation.goBack() is called synchronously after the promise resolves inside handleModalHide, which can fire before the underlying animation is truly done.
PR #88603 changed the card deactivation flow in WorkspaceExpensifyCardDetailsPage.tsx. The old code used InteractionManager.runAfterInteractions to defer deactivateCardAction until after animations completed, and triggered Navigation.goBack() via the modal's onModalHide callback — so navigation happened before the card data was deleted from Onyx.
The new code calls deactivateCardAction before Navigation.goBack(). Since deactivateCardAction optimistically sets the card to null in Onyx, React processes that state change and re-renders the component with card=null before goBack() removes the screen from the navigation stack. This triggers the NotFoundPage guard at line 221, causing the brief error page flash.
What changes do you think we should make in order to solve the problem?
Swap the order at lines 136-137: call Navigation.goBack()beforedeactivateCardAction(defaultFundID, card). This restores the old semantics where navigation away starts before the card data is removed from Onyx, preventing the NotFoundPage flash.
What alternative solutions did you explore? (Optional)
An alternative would be to add a "navigating away" guard that suppresses the NotFoundPage render when a goBack is already in progress, but swapping the call order is simpler and directly matches the pre-regression behavior.
Investigation details
Data flow causing the bug:
User confirms deactivation → showConfirmModal promise resolves
deactivateCardAction(defaultFundID, card) runs — optimistically sets card to null in Onyx
Navigation.goBack() runs — but React processes the Onyx update first
Component re-renders with card=null → NotFoundPage flashes
Navigation animation completes and removes the screen
The old pattern ensured goBack() fired via onModalHide before deactivateCardAction ran (deferred by InteractionManager).
Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.
📣 @Ilya0527! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.75-14 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
@Pujan92 The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
changed the title [-][Due for payment 2026-05-26] Expensify Card - Error page briefly shown when deactivate Expensify Card[/-][+][Due for payment 2026-05-26] [Due for payment 2026-05-26] Expensify Card - Error page briefly shown when deactivate Expensify Card[/+]on May 19, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.77-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
@Pujan92 The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
Activity
melvin-bot commentedon May 16, 2026
You have been assigned to this deploy blocker because you recently merged this PR: #88603
melvin-bot commentedon May 16, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon May 16, 2026
👋 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:
OSBotify commentedon May 16, 2026
Investigation Summary
Classification: Frontend bug
Causing PR: #88603 - "Migrate InteractionManager - batch 1" by @mrejdak (High confidence)
Related Issues: None
Recommendation: REVERT
The PR introduced a timing regression in the Expensify Card deactivation flow. The fix needs to happen in the App repo by either reverting the PR or applying a targeted fix to restore the navigation-after-animation-completes behavior.
Assigned: @mrejdak (PR author), @collectioneur, @Pujan92, @roryabraham (approving reviewers)
Labels:
DeployBlockerCashkept (Frontend bug in App). No label changes made.Detailed Analysis
Evidence
PR #88603 migrated
WorkspaceExpensifyCardDetailsPage.tsxaway fromInteractionManagerand the oldConfirmModalpattern to the newuseConfirmModal/showConfirmModalpromise-based API.The bug is staging-only (not reproducible in production), and PR #88603 was merged on 2026-05-14 and is included in the current
9.3.75-1staging deploy checklist.The issue is directly triggered by the deactivation flow change in this PR.
Verification
src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsxgh pr view 88603 --json files)showConfirmModalandNavigation.goBack()is now called immediately after the promise resolvesRoot Cause
Old behavior (before PR #88603):
The
ConfirmModalwas rendered inline withshouldSetModalVisibility={false}and navigation was triggered via theonModalHidecallback, which fires only after the modal's closing animation has fully completed. This guaranteed thatNavigation.goBack()was never called while the modal was still animating closed.New behavior (after PR #88603):
deactivateCard()is now an async function that awaitsshowConfirmModal(...). When the user confirms,ConfirmModalWrapper.handleConfirm()setsisVisible = false(starting the close animation) and thenonModalHidefires — which callscloseModal(), resolving the promise. At that point, theawaitresumes andNavigation.goBack()is called.The issue is that the promise resolves at the start of the
onModalHidecallback, but the modal's closing animation may not be fully complete at that point whenshouldSetModalVisibility={false}is passed. Navigating away while the modal overlay is still visible causes the screen to briefly render an error page before the modal finishes closing.The original code explicitly used
onModalHideto defer navigation until after the animation. The new code loses this guarantee becauseNavigation.goBack()is called synchronously after the promise resolves insidehandleModalHide, which can fire before the underlying animation is truly done.MelvinBot commentedon May 16, 2026
Proposal
What is the root cause of that problem?
PR #88603 changed the card deactivation flow in
WorkspaceExpensifyCardDetailsPage.tsx. The old code usedInteractionManager.runAfterInteractionsto deferdeactivateCardActionuntil after animations completed, and triggeredNavigation.goBack()via the modal'sonModalHidecallback — so navigation happened before the card data was deleted from Onyx.The new code calls
deactivateCardActionbeforeNavigation.goBack(). SincedeactivateCardActionoptimistically sets the card tonullin Onyx, React processes that state change and re-renders the component withcard=nullbeforegoBack()removes the screen from the navigation stack. This triggers theNotFoundPageguard at line 221, causing the brief error page flash.What changes do you think we should make in order to solve the problem?
Swap the order at lines 136-137: call
Navigation.goBack()beforedeactivateCardAction(defaultFundID, card). This restores the old semantics where navigation away starts before the card data is removed from Onyx, preventing theNotFoundPageflash.What alternative solutions did you explore? (Optional)
An alternative would be to add a "navigating away" guard that suppresses the
NotFoundPagerender when agoBackis already in progress, but swapping the call order is simpler and directly matches the pre-regression behavior.Investigation details
Data flow causing the bug:
showConfirmModalpromise resolvesdeactivateCardAction(defaultFundID, card)runs — optimistically sets card tonullin OnyxNavigation.goBack()runs — but React processes the Onyx update firstcard=null→NotFoundPageflashesOld code (before PR #88603):
The old pattern ensured
goBack()fired viaonModalHidebeforedeactivateCardActionran (deferred byInteractionManager).Next Steps for Contributor+ team: Reply with
@MelvinBot implement thisto create a draft PR,@MelvinBot <your feedback>to refine this analysis, or explain why you are rejecting Melvin's proposal.Pujan92 commentedon May 16, 2026
@mrejdak needs to fix this by navigating back first and then making a callback after the transition here.
App/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx
Lines 136 to 137 in 809efef
3 remaining items
melvin-bot commentedon May 19, 2026
📣 @Ilya0527! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:
Format:
[-]Expensify Card - Error page briefly shown when deactivate Expensify Card[/-][+][Due for payment 2026-05-26] Expensify Card - Error page briefly shown when deactivate Expensify Card[/+]melvin-bot commentedon May 19, 2026
Reviewinglabel has been removed, please complete the "BugZero Checklist".melvin-bot commentedon May 19, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.75-14 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-05-26. 🎊
For reference, here are some details about the assignees on this issue:
melvin-bot commentedon May 19, 2026
@Pujan92 The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
[-][Due for payment 2026-05-26] Expensify Card - Error page briefly shown when deactivate Expensify Card[/-][+][Due for payment 2026-05-26] [Due for payment 2026-05-26] Expensify Card - Error page briefly shown when deactivate Expensify Card[/+]melvin-bot commentedon May 19, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.77-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-05-26. 🎊
For reference, here are some details about the assignees on this issue:
melvin-bot commentedon May 19, 2026
@Pujan92 The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]
Pujan92 commentedon May 20, 2026
@roryabraham this issue can be closed.