Repository navigation
[Due for payment 2026-06-03] [$250] Add BA - Bank account is still shown in Payments after tapping Start over offline #89895
Description
Activity
- addedBugSomething is broken. Auto assigns a BugZero manager.Something is broken. Auto assigns a BugZero manager.
on May 7, 2026 Proposal
What is the root cause of that problem?
resetUSDBankAccount(resetUSDBankAccount.ts) clearsREIMBURSEMENT_ACCOUNTandpolicy.achAccountoptimistically, but never touchesBANK_ACCOUNT_LIST. TheWorkspaceWorkflowsPagedetermines whether to show a pending bank account by searchingBANK_ACCOUNT_LISTfor an entry whosepolicyIDmatches the workspace. Since that entry is never removed, the pending bank account row persists across app restarts while offline.What changes do you think we should make in order to solve the problem?
Add
BANK_ACCOUNT_LISTupdates toresetUSDBankAccount, following the existing pattern indeletePaymentBankAccount:- optimisticData: Set
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETEon the bank account entry inBANK_ACCOUNT_LIST - successData: Set the entry to
nullto remove it - failureData: Restore the original entry
The same fix should be applied to
resetNonUSDBankAccountwhich has the identical omission.Investigation details
Data flow (why the bug occurs):
- User taps "Start over" →
WorkspaceResetBankAccountModalcallsresetUSDBankAccount() - Optimistic update clears
REIMBURSEMENT_ACCOUNT.achDataandpolicy.achAccount, butBANK_ACCOUNT_LISTis untouched - Offline, so
API.write()queuesRestartBankAccountSetupfor later - App kill + relaunch → Onyx restores from disk, including the still-present
BANK_ACCOUNT_LISTentry WorkspaceWorkflowsPageline 260 finds the entry and shows the pending bank account row- Even after going online and the queued API call succeeding, the client-side
successDataalso omitsBANK_ACCOUNT_LISTcleanup — the entry persists until a full server data refresh
Contrast with working pattern:
deletePaymentBankAccountinBankAccounts.ts:583-611correctly managesBANK_ACCOUNT_LISTin all three data phases (optimistic/success/failure).
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.- optimisticData: Set
- addedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributor
on May 7, 2026 - addedHelp WantedApply this label when an issue is open to proposals by contributorsApply this label when an issue is open to proposals by contributors
on May 7, 2026 Proposal
What is the root cause of that problem?
When the user taps "Start over" on a pending bank account,
resetUSDBankAccountonly clearsREIMBURSEMENT_ACCOUNT.achDataandpolicy.achAccountinoptimisticDatabut never touches theBANK_ACCOUNT_LIST[bankAccountID]entry — and neither doessuccessData/failureData.App/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts
Lines 42 to 146 in d6b3ccf
optimisticData: [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: { shouldShowResetModal: false, isLoading: true, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, achData: null, }, }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { achAccount: null, }, }, ], successData: [ { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.ONFIDO_TOKEN, value: '', }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.ONFIDO_APPLICANT_ID, value: '', }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.PLAID_DATA, value: CONST.PLAID.DEFAULT_DATA, }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, value: '', }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA, }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, value: { [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.OWNS_MORE_THAN_25_PERCENT]: false, [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.HAS_OTHER_BENEFICIAL_OWNERS]: false, [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNERS]: '', [INPUT_IDS.BANK_INFO_STEP.ACCOUNT_NUMBER]: '', [INPUT_IDS.BANK_INFO_STEP.ROUTING_NUMBER]: '', [INPUT_IDS.BANK_INFO_STEP.PLAID_ACCOUNT_ID]: '', [INPUT_IDS.BANK_INFO_STEP.PLAID_MASK]: '', [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_NAME]: '', [INPUT_IDS.BUSINESS_INFO_STEP.STREET]: '', [INPUT_IDS.BUSINESS_INFO_STEP.CITY]: '', [INPUT_IDS.BUSINESS_INFO_STEP.STATE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.ZIP_CODE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_PHONE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_WEBSITE]: undefined, [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_TAX_ID]: '', [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_TYPE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_DATE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_STATE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.HAS_NO_CONNECTION_TO_CANNABIS]: false, [INPUT_IDS.PERSONAL_INFO_STEP.FIRST_NAME]: '', [INPUT_IDS.PERSONAL_INFO_STEP.LAST_NAME]: '', [INPUT_IDS.PERSONAL_INFO_STEP.STREET]: '', [INPUT_IDS.PERSONAL_INFO_STEP.CITY]: '', [INPUT_IDS.PERSONAL_INFO_STEP.STATE]: '', [INPUT_IDS.PERSONAL_INFO_STEP.ZIP_CODE]: '', [INPUT_IDS.PERSONAL_INFO_STEP.IS_ONFIDO_SETUP_COMPLETE]: false, [INPUT_IDS.PERSONAL_INFO_STEP.DOB]: '', [INPUT_IDS.PERSONAL_INFO_STEP.SSN_LAST_4]: '', [INPUT_IDS.COMPLETE_VERIFICATION.ACCEPT_TERMS_AND_CONDITIONS]: false, [INPUT_IDS.COMPLETE_VERIFICATION.CERTIFY_TRUE_INFORMATION]: false, [INPUT_IDS.COMPLETE_VERIFICATION.IS_AUTHORIZED_TO_USE_BANK_ACCOUNT]: false, [INPUT_IDS.BANK_INFO_STEP.IS_SAVINGS]: false, [INPUT_IDS.BANK_INFO_STEP.BANK_NAME]: '', [INPUT_IDS.BANK_INFO_STEP.PLAID_ACCESS_TOKEN]: '', [INPUT_IDS.BANK_INFO_STEP.SELECTED_PLAID_ACCOUNT_ID]: '', [INPUT_IDS.AMOUNT1]: '', [INPUT_IDS.AMOUNT2]: '', [INPUT_IDS.AMOUNT3]: '', }, }, ], failureData: [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: {isLoading: false, pendingAction: null}, }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { achAccount, }, }, ], }; The Workflows page renders the pending row from
BANK_ACCOUNT_LIST(filtered bypolicyID), not just frompolicy.achAccount:App/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
Lines 259 to 270 in d6b3ccf
const isBankAccountFullySetup = policy?.achAccount && (policy?.achAccount.state === CONST.BANK_ACCOUNT.STATE.OPEN || policy?.achAccount.state === CONST.BANK_ACCOUNT.STATE.LOCKED); const bankAccountConnectedToWorkspace = Object.values(bankAccountList ?? {}).find((bankAccount) => bankAccount?.accountData?.additionalData?.policyID === policy?.id); const bankName = isBankAccountFullySetup ? (policy?.achAccount?.bankName ?? '') : (bankAccountConnectedToWorkspace?.accountData?.additionalData?.bankName ?? ''); const addressName = isBankAccountFullySetup ? (policy?.achAccount?.addressName ?? '') : (bankAccountConnectedToWorkspace?.accountData?.addressName ?? ''); const accountData = isBankAccountFullySetup ? policy?.achAccount : bankAccountConnectedToWorkspace?.accountData; const bankTitle = addressName.includes(CONST.MASKED_PAN_PREFIX) ? bankName : addressName; const bankAccountID = isBankAccountFullySetup ? policy?.achAccount?.bankAccountID : bankAccountConnectedToWorkspace?.methodID; const state = isBankAccountFullySetup ? (policy?.achAccount?.state ?? '') : (bankAccountConnectedToWorkspace?.accountData?.state ?? ''); const isAccountInSetupState = isBankAccountPartiallySetup(state); const isBusinessBankAccountLocked = state === CONST.BANK_ACCOUNT.STATE.LOCKED; const shouldShowBankAccount = (!!isBankAccountFullySetup || !!bankAccountConnectedToWorkspace) && policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO; So the offline flow plays out like this:
- Offline "Start over" applies the optimistic update —
achData/policy.achAccountgo tonull, but thebankAccountList[bankAccountID]entry is untouched. TheRESTART_BANK_ACCOUNT_SETUPrequest is queued inPersistedRequests. - The user kills and re‑launches the app while still offline. Onyx is re‑hydrated and
policy.achAccountis null at this point, but thebankAccountListentry still exists. - Going online flushes the queued request. The Pusher update that removes the bank account from
bankAccountListcan be missed in this offline → kill → online sequence (this is the same edge casecloseAccountalready guards against, see the comment at). With no client‑side cleanup ofApp/src/libs/actions/BankAccounts.ts
Lines 592 to 600 in d6b3ccf
// Sometimes pusher updates aren't received when we close the App while still offline, // so we are setting the bankAccount to null here to ensure that it gets cleared out once we come back online. successData: [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`, value: {[bankAccountID]: null}, }, ], bankAccountList, the row remains, and becauseOpenWorkspacere‑hydratespolicy.achAccountfrom the BE before/while the queued request is processed, "Continue setup / Start over" comes back too.
What changes do you think we should make in order to solve the problem?
I'd add a
successDatawrite that nulls thebankAccountList[bankAccountID]entry, matching the pattern already used byclosePaymentBankAccount. That guarantees the row is cleaned up on the client even if the Pusher update is dropped due to the offline → kill → online sequence.In
src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts, extendsuccessData:successData: [ + { + // Sometimes pusher updates aren't received when we close the App while still offline, + // so we are setting the bankAccount to null here to ensure that it gets cleared out once we come back online. + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.BANK_ACCOUNT_LIST, + value: {[bankAccountID]: null}, + }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.ONFIDO_TOKEN, value: '', },
To keep the offline UX consistent (the row should disappear immediately after confirming "Start over"), I'd also mark the entry as
pendingAction: DELETEoptimistically and restore it on failure:optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.BANK_ACCOUNT_LIST, + value: {[bankAccountID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}}, + }, { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
Supporting changes:
- Apply the same three
BANK_ACCOUNT_LISTupdates (optimisticpendingAction: DELETE, successnull, failure{pendingAction: null, errors: ...}) insrc/libs/actions/ReimbursementAccount/resetNonUSDBankAccount.ts, which has the identical gap for non‑USD workspaces. - The Workflows page already hides rows whose
bankAccountConnectedToWorkspaceresolves to a deleted entry, so no UI changes are needed.
What alternative solutions did you explore? (Optional)
- Clearing
BANK_ACCOUNT_LIST[bankAccountID]directly inoptimisticData(instead of markingpendingAction: DELETEthen nulling on success). This works but loses the ability to restore the row on a real BE failure, and breaks the offline pending‑state UX that other delete flows already use. - Re‑fetching
OpenWorkspaceafter going online to refreshpolicy.achAccount. This doesn't address the dropped‑Pusher case forbankAccountListand adds an extra round trip — the localizedsuccessDatacleanup is cheaper and self‑contained.
- Offline "Start over" applies the optimistic update —
Proposal
What is the root cause of that problem?
The Workflows screen reads the pending bank account from two Onyx keys:
policy.achAccount(when the BA is fully verified) andbankAccountList(when it is still in setup). The relevant lookup is here —bankAccountConnectedToWorkspaceis whatever entry inBANK_ACCOUNT_LISThasadditionalData.policyID === policy.id:App/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
Lines 259 to 270 in d6b3ccf
const isBankAccountFullySetup = policy?.achAccount && (policy?.achAccount.state === CONST.BANK_ACCOUNT.STATE.OPEN || policy?.achAccount.state === CONST.BANK_ACCOUNT.STATE.LOCKED); const bankAccountConnectedToWorkspace = Object.values(bankAccountList ?? {}).find((bankAccount) => bankAccount?.accountData?.additionalData?.policyID === policy?.id); const bankName = isBankAccountFullySetup ? (policy?.achAccount?.bankName ?? '') : (bankAccountConnectedToWorkspace?.accountData?.additionalData?.bankName ?? ''); const addressName = isBankAccountFullySetup ? (policy?.achAccount?.addressName ?? '') : (bankAccountConnectedToWorkspace?.accountData?.addressName ?? ''); const accountData = isBankAccountFullySetup ? policy?.achAccount : bankAccountConnectedToWorkspace?.accountData; const bankTitle = addressName.includes(CONST.MASKED_PAN_PREFIX) ? bankName : addressName; const bankAccountID = isBankAccountFullySetup ? policy?.achAccount?.bankAccountID : bankAccountConnectedToWorkspace?.methodID; const state = isBankAccountFullySetup ? (policy?.achAccount?.state ?? '') : (bankAccountConnectedToWorkspace?.accountData?.state ?? ''); const isAccountInSetupState = isBankAccountPartiallySetup(state); const isBusinessBankAccountLocked = state === CONST.BANK_ACCOUNT.STATE.LOCKED; const shouldShowBankAccount = (!!isBankAccountFullySetup || !!bankAccountConnectedToWorkspace) && policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO; When the user taps Start over in the BA setup,
resetUSDBankAccountonly clearsREIMBURSEMENT_ACCOUNTandpolicy.achAccount; it never touchesBANK_ACCOUNT_LIST:App/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts
Lines 42 to 131 in d6b3ccf
optimisticData: [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: { shouldShowResetModal: false, isLoading: true, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, achData: null, }, }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { achAccount: null, }, }, ], successData: [ { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.ONFIDO_TOKEN, value: '', }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.ONFIDO_APPLICANT_ID, value: '', }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.PLAID_DATA, value: CONST.PLAID.DEFAULT_DATA, }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, value: '', }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA, }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, value: { [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.OWNS_MORE_THAN_25_PERCENT]: false, [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.HAS_OTHER_BENEFICIAL_OWNERS]: false, [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNERS]: '', [INPUT_IDS.BANK_INFO_STEP.ACCOUNT_NUMBER]: '', [INPUT_IDS.BANK_INFO_STEP.ROUTING_NUMBER]: '', [INPUT_IDS.BANK_INFO_STEP.PLAID_ACCOUNT_ID]: '', [INPUT_IDS.BANK_INFO_STEP.PLAID_MASK]: '', [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_NAME]: '', [INPUT_IDS.BUSINESS_INFO_STEP.STREET]: '', [INPUT_IDS.BUSINESS_INFO_STEP.CITY]: '', [INPUT_IDS.BUSINESS_INFO_STEP.STATE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.ZIP_CODE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_PHONE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_WEBSITE]: undefined, [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_TAX_ID]: '', [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_TYPE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_DATE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_STATE]: '', [INPUT_IDS.BUSINESS_INFO_STEP.HAS_NO_CONNECTION_TO_CANNABIS]: false, [INPUT_IDS.PERSONAL_INFO_STEP.FIRST_NAME]: '', [INPUT_IDS.PERSONAL_INFO_STEP.LAST_NAME]: '', [INPUT_IDS.PERSONAL_INFO_STEP.STREET]: '', [INPUT_IDS.PERSONAL_INFO_STEP.CITY]: '', [INPUT_IDS.PERSONAL_INFO_STEP.STATE]: '', [INPUT_IDS.PERSONAL_INFO_STEP.ZIP_CODE]: '', [INPUT_IDS.PERSONAL_INFO_STEP.IS_ONFIDO_SETUP_COMPLETE]: false, [INPUT_IDS.PERSONAL_INFO_STEP.DOB]: '', [INPUT_IDS.PERSONAL_INFO_STEP.SSN_LAST_4]: '', [INPUT_IDS.COMPLETE_VERIFICATION.ACCEPT_TERMS_AND_CONDITIONS]: false, [INPUT_IDS.COMPLETE_VERIFICATION.CERTIFY_TRUE_INFORMATION]: false, [INPUT_IDS.COMPLETE_VERIFICATION.IS_AUTHORIZED_TO_USE_BANK_ACCOUNT]: false, [INPUT_IDS.BANK_INFO_STEP.IS_SAVINGS]: false, [INPUT_IDS.BANK_INFO_STEP.BANK_NAME]: '', [INPUT_IDS.BANK_INFO_STEP.PLAID_ACCESS_TOKEN]: '', [INPUT_IDS.BANK_INFO_STEP.SELECTED_PLAID_ACCOUNT_ID]: '', [INPUT_IDS.AMOUNT1]: '', [INPUT_IDS.AMOUNT2]: '', [INPUT_IDS.AMOUNT3]: '', }, }, ], Online, this normally works because the backend deletes the entry and Pusher pushes a fresh
BANK_ACCOUNT_LISTto the client. But once you go through offline → Start over → kill app → relaunch → online, the Pusher event is missed in the offline window (the app already documents this race indeletePaymentBankAccount: "Sometimes pusher updates aren't received when we close the App while still offline, so we are setting the bankAccount to null here to ensure that it gets cleared out once we come back online" —). So the queuedApp/src/libs/actions/BankAccounts.ts
Lines 592 to 600 in d6b3ccf
// Sometimes pusher updates aren't received when we close the App while still offline, // so we are setting the bankAccount to null here to ensure that it gets cleared out once we come back online. successData: [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`, value: {[bankAccountID]: null}, }, ], RESTART_BANK_ACCOUNT_SETUPsucceeds, but the staleBANK_ACCOUNT_LIST[bankAccountID]survives,bankAccountConnectedToWorkspacekeeps resolving truthy, and the row keeps rendering with Continue setup / Start over.resetNonUSDBankAccounthas the same omission:App/src/libs/actions/ReimbursementAccount/resetNonUSDBankAccount.ts
Lines 43 to 74 in d6b3ccf
optimisticData: [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: { shouldShowResetModal: false, isLoading: true, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, achData: null, }, }, ], successData: [ { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, value: null, }, { onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA, }, ], failureData: [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: {isLoading: false, pendingAction: null}, }, ], }; What changes do you think we should make in order to solve the problem?
I'd mirror the pattern from
deletePaymentBankAccountin both reset flows: optimistically mark theBANK_ACCOUNT_LISTentry for delete (so the Workflows row hides immediately offline), null it out on success (so the local list is correct even if Pusher is lost), and restore it on failure. SincebankAccount.methodID === policy.achAccount.bankAccountID(confirmed inSettlementButton/index.tsx:631), the samebankAccountIDwe already have keys intoBANK_ACCOUNT_LIST.In
src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts:optimisticData: [ { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, value: { shouldShowResetModal: false, isLoading: true, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, achData: null, }, }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { achAccount: null, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.BANK_ACCOUNT_LIST, + value: {[bankAccountID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}}, + }, ],successData: [ // ...existing resets for ONFIDO_TOKEN, PLAID_DATA, REIMBURSEMENT_ACCOUNT, FORM_DRAFT, ... + // Sometimes pusher updates aren't received when we close the App while still offline, + // so we set the bankAccount to null here to ensure it's cleared out once we come back online. + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.BANK_ACCOUNT_LIST, + value: {[bankAccountID]: null}, + }, ],failureDatashould add a matching{[bankAccountID]: {pendingAction: null}}so the row reappears cleanly if the request errors.resetNonUSDBankAccount.tsneeds the same three additions inside theif (bankAccountID)branch (the early-return branch has no bankAccountID and no list entry to clean up, so it's untouched).After this, the Workflows screen and any other consumer of
bankAccountListagree withpolicy.achAccountimmediately, regardless of whether the Pusher push lands.What alternative solutions did you explore? (Optional)
- Filter
bankAccountListagainstpolicy.achAccountin the Workflows selector — i.e. don't showbankAccountConnectedToWorkspaceifpolicy.achAccountwas just nulled. Rejected: it papers over an inconsistent local store and every other reader ofBANK_ACCOUNT_LIST(PaymentMethodList, Wallet, SettlementButton) would still see the ghost entry until the next refresh. - Re-fetch
OpenReimbursementAccountPageon reconnect to repopulateBANK_ACCOUNT_LISTfrom the server. Rejected: still leaves a visible window where the row is shown with Continue setup, and adds a network round-trip for something the client already knows is gone. - Clear the entry by scanning
bankAccountListforadditionalData.policyID === policyIDinstead of usingbankAccountID. Rejected: extra lookup with no benefit sincemethodID === bankAccountIDis already the contract used inSettlementButton.
- Filter
Triggered auto assignment to Contributor-plus team member for initial proposal review - @eVoloshchak (
External)- changed the title
[-]Add BA - Bank account is still shown in Payments after tapping Start over offline[/-][+][$250] Add BA - Bank account is still shown in Payments after tapping Start over offline[/+]on May 7, 2026 Job added to Upwork: https://www.upwork.com/jobs/~022052317083169401266
⚠️ @mukhrr Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).- addedInternalRequires API changes or must be handled by Expensify staffRequires API changes or must be handled by Expensify staffand removedExternalAdded to denote the issue can be worked on by a contributorAdded to denote the issue can be worked on by a contributor
on May 7, 2026 29 remaining items
@eVoloshchak 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]
@eVoloshchak Huh... This is 4 days overdue. Who can take care of this?
- addedAwaiting PaymentAuto-added when associated PR is deployed to productionAuto-added when associated PR is deployed to production
on Jun 2, 2026 Triggered auto assignment to @mallenexpensify (
Awaiting Payment)Payment Summary
- Reviewer: @eVoloshchak owed $250 via NewDot
BugZero Checklist (@mallenexpensify)
- I have verified the correct assignees and roles are listed above and updated the necessary manual offers
- I have verified that there are no duplicate or incorrect contracts on Upwork for this job (https://www.upwork.com/ab/applicants/2052317083169401266/hired)
- I have verified the PR was not reverted
- I have applied any discounts due to bugs/regressions introduced by this PR
- I have paid out the Upwork contracts or cancelled the ones that are incorrect
- I have verified the payment summary above is correct
Payment Summary
Contributor+: @eVoloshchak due $250 via NewDot
@eVoloshchak plz complete the BZ checklist and tag me in a post once you have. Thx
BugZero 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, this was not implemented initially
Link to the comment on the Issue: -
[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: regression wasn't critical
-
[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.
-
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Link to issue: https://github.com/Expensify/Expensify/issues/646588
Regression Test Proposal
Test:
- Sign in to ND
- Create a workspace
- Navigate to Workspace settings > Workflows > Add bank account
- Start connecting a bank account flow (Plaid 1111, Alberta Charleson), exit the flow after entering the first and last name
- Tap the pending bank account row
- Tap "Start over" and confirm
- Verify the pending bank account item is removed from Workflows
- Verify that no errors appear in the JS console
Do we agree 👍 or 👎
Zapier Logs
Run ID: 00040eee-3bd3-a66e-fb97-601635d199a4-
@mallenexpensify, checklist completed. Thx!
Reacted by Matt Allen$250 approved for @eVoloshchak
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!
Version Number: 9.3.67.11
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/13176694/test-cases/43147208
Email or phone of affected tester (no customers): N/A
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: iPhone 15 iOS 26.3.1
App Component: Workspace Settings
Action Performed:
Expected Result:
Pending bank account disappears from Payments after selecting "Start over" offline, refreshing the app, and going online
Actual Result:
Pending bank account is still displayed in Payments after selecting "Start over" offline, refreshing the app, and going online. After opening it, "Continue setup" and "Start over" options are shown.
Workaround:
Unknown
Platforms:
Screenshots/Videos
Bug7146311_1778088071736.Start_over_offline.mp4
View all open jobs on GitHub
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @eVoloshchak