Skip to content

[Due for payment 2026-10-12] Workspace - Back button reopens upgrade success after refreshing upgrade page and upgrade #102184

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.93-0
Reproducible in staging?: Yes
Reproducible in production?: N/A - new feature, doesn't exist in prod
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #101710
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: Mac 26.5 / Chrome
App Component: Workspace Settings

Action Performed:

Precondition:

  • Create two workspaces - A and B.
  • Workspace A is Control workspace.
  • Workspace B is Collect workspace.
  • For workspace A, enable Rules and enable attendee tracking.
  1. Go to staging.new.expensify.com
    2 Go to Workspaces.
  2. Click 3-dot menu on workspace A > Copy settings.
  3. Select workspace B > Next.
  4. Select Rules > Next.
  5. Refresh Upgrade RHP.
  6. Click Upgrade > Continue.
  7. Click RHP back button three times.

Expected Result:

RHP will close.

Actual Result:

Upgrade success page reopens.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7268218_1790254623811.1.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Svetlana Lazutkina
Bug ID: 7268218

Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 24, 2026
  2. applause-bot commented on Sep 24, 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/7262dbecacd183531819db5d4c28d3ee5e6154e7aa2dff0924051889322ac373

  3. melvin-bot commented on Sep 24, 2026

    @melvin-bot

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

  4. melvin-bot commented on Sep 24, 2026

    @melvin-bot

    @marcochavezf @JmillsExpensify @daledah you have been assigned to this deploy blocker because you reviewed the offending PR: #101710

  5. melvin-bot commented on Sep 24, 2026

    @melvin-bot

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

  6. github-actions commented on Sep 24, 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.
  7. MelvinBot commented on Sep 24, 2026

    @MelvinBot
    Contributor

    🔍 Investigation Summary

    Classification: Frontend bug
    Causing PR: [Bulk workspace edits] Release 2: remove Control-only restriction + Upgrade page (Expensify/App#94060) by @fedirjh (High confidence) — merged 2026-07-08, already in production
    Not the cause: Show the Upgrade step when copying rules to a Collect workspace (Expensify/App#101710) by @daledah — refuted below (High confidence). It only made an existing broken screen reachable from one more entry point.
    Related Issues: Copying "Rules" from Control to Collect fails instead of offering the Upgrade step (Expensify/App#101456) — the issue #101710 fixed

    Recommendation: ROLL FORWARD

    The defective code is CopyPolicySettingsUpgradePage, which is byte-identical on staging and production, so there is nothing on this checklist to revert — reverting #101710 would only hide one entry point while reintroducing the Control→Collect Rules copy failure it fixed. The fix is a one-line change: acknowledge the upgrade with Navigation.navigate(..., {forceReplace: true}) so the completed Upgrade step leaves the stack.

    Assigned: @fedirjh (PR author), @shubham1206agra and @yuwenmemon (approving reviewers) from #94060, the highest-confidence causing PR
    Labels: No changes. DeployBlockerCash stays (App-side fix, and the issue is not confirmed non-reproducible in production — see below). DeployBlocker was never applied.

    📋 Detailed Analysis

    Evidence

    #101710 did not introduce the defect. Its entire diff is 16 added / 3 removed lines in src/libs/CopyPolicySettingsUtils.ts plus test updates. It adds one early return so rules and codingRules count as Control-only for a Collect target, which routes Select settings to the Upgrade step instead of straight to Confirm. It touches no navigation code, no page component and no local state.

    The broken screen is unchanged from production. Diffing production...main for the whole copy-settings area returns no change at all to CopyPolicySettingsUpgradePage.tsx, CopyPolicySettingsConfirmPage.tsx, CopyPolicySettingsSelectWorkspacesPage.tsx, the copy-settings routes in linkingConfig/config.ts, or Navigation.ts. The only staging-only changes in that area are #101710's gating lines and an unrelated member-count selector tweak in the Select features page.

    The Upgrade step is already reachable in production, so this should reproduce there. canPolicyAccessFeature is identical on production and staging and treats ARE_PER_DIEM_RATES_ENABLED and ARE_INVOICE_FIELDS_ENABLED as Control-only. Via PART_TO_POLICY_FEATURE that means Per diem (and Invoices when the source has invoice fields) already routes a Collect target through the same Upgrade step on production today. The same steps — copy Per diem from a Control workspace to a Collect workspace, refresh the Upgrade RHP, upgrade, then press Back three times — should reproduce this on production. The "N/A — new feature" note in the report is true only of the Rules path, not of the Upgrade step itself. Worth re-testing that path before treating this as staging-only.

    The copy-settings entry point is not beta gated (WorkspaceRowThreeDotsMenu only checks isAdmin and isEligibleToCopy), so nothing else keeps this off production.

    Verification

    Affected file: src/pages/workspace/copyPolicySettings/CopyPolicySettingsUpgradePage.tsx, added by #94060 and unmodified since (only two follow-ups, a label-fallback cleanup and a flicker fix, have touched it — neither changed the navigation or the hasRequestedUpgrade state).

    Files changed by #101710: src/libs/CopyPolicySettingsUtils.ts and tests/unit/CopyPolicySettingsUtilsTest.ts only. It does not modify the affected file, which is why the auto-assignment is refuted.

    Root Cause

    The Upgrade step keeps its "the user already upgraded" flag in component state (hasRequestedUpgrade, L67) and then moves forward with a push (navigateToConfirm, L102-L107), so the completed, non-repeatable step stays mounted underneath the Confirm screen with showSuccess latched to true.

    The refresh in step 6 is what makes that stale screen reachable. On a refresh, getAdaptedStateFromPath only prepends the full-screen route under the RHP; it does not synthesize the earlier wizard steps, so the copy-settings stack is just [Upgrade]. From there:

    1. Continue pushes Confirm → [Upgrade, Confirm], with Upgrade still mounted and hasRequestedUpgrade === true.
    2. Back Some initial fixes and code style updates #1 on Confirm calls goBack(POLICY_COPY_SETTINGS_SELECT_FEATURES). Select features is not in the rebuilt stack, so canPopToRoute is false and goUp dispatches a REPLACE → [Upgrade, Select features].
    3. Back Fix spaces #2 on Select features calls goBack(POLICY_COPY_SETTINGS) → same REPLACE → [Upgrade, Select workspaces].
    4. Back Add Expensify eslint #3 on Select workspaces is a plain Navigation.goBack with no fallback, so it pops onto the stale Upgrade screen instead of closing the RHP. hasRequestedUpgrade is still true and policiesToUpgrade is now empty (the targets are Control), so showSuccess is true and "You've upgraded your workspace!" reappears — with shouldShowBackButton={!showSuccess} hiding the back button, dead-ending the pane.

    Without the refresh, Select features is in the stack, so Back #1 pops two screens at once and the Upgrade screen never survives — which is exactly why step 6 is a required precondition.

    The redirect effect at L85-L90, whose comment says it exists to handle "back navigation after upgrading", cannot help: it returns early when hasRequestedUpgrade is true, which is precisely this case.

    Suggested fix (one line, in navigateToConfirm), matching the pattern WorkspaceUpgradePage already uses for its own post-upgrade hop:

    // The upgrade is one-way, so replace this step instead of pushing on top of it. Otherwise a
    // refreshed stack leaves it underneath and Back lands on the stale success view.
    Navigation.navigate(ROUTES.POLICY_COPY_SETTINGS_CONFIRM.getRoute(sourcePolicyID), {forceReplace: true});

    Regression check for the fix: after upgrading, Back from Confirm should reach Select features (or close the RHP when it was deep-linked), and re-pressing Next there should go straight to Confirm, since the targets are now Control.

    💡 Suggested AI code review rule

    This bug looks like a recurring, mechanically detectable pattern, so it may be worth a coding-standards rule so the AI reviewer catches the next one. Proposed for Expensify/App (.claude/skills/app-coding-standards):

    • Rule: UI-4 Leave one-way flow steps behind with forceReplace
    • Condition: When a wizard/RHP step is one-way (upgrade completed, payment taken, success acknowledged) and latches that fact in local useState, move to the next step with Navigation.navigate(route, {forceReplace: true}) instead of a plain push. A screen that hides its own back button in a terminal state (shouldShowBackButton={!someTerminalState}) must not stay in the stack.
    • Why: A refresh or deep link rebuilds the RHP with only the current screen, so goBack(fallback) REPLACEs rather than pops; the completed step lingers underneath, and a later Back re-renders its latched success view with no way out. Incident: this deploy blocker.
    • Search pattern: rg -n "shouldShowBackButton=\{!" src/pages and rg -n -B2 -A6 "afterUpgradeAcknowledged|onSuccess|navigateTo(Confirm|Next)" src/pages | rg -v "forceReplace" — flag forward navigation out of a terminal state that lacks {forceReplace: true}.
    • Offending code (❌):
      const navigateToConfirm = () => {
      if (!sourcePolicyID) {
      return;
      }
      Navigation.navigate(ROUTES.POLICY_COPY_SETTINGS_CONFIRM.getRoute(sourcePolicyID));
      };
    • Fix (✅): the same call with {forceReplace: true}, as in
      const afterUpgradeAcknowledged = () => {
      if (feature?.id === CONST.UPGRADE_FEATURE_INTRO_MAPPING.companyCards.id && policyID) {
      Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.path, route.params.backTo ?? ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)), {
      forceReplace: true,
      });
      return;
      }
      goBack();
      };

    The full rule file, with the ❌/✅ code inline in App's coding-standards format, would go in Expensify/App itself. If it's worth adding, a maintainer can open it there as a draft PR.

  8. 27 remaining items

  9. melvin-bot commented on Oct 11, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  10. melvin-bot commented on Oct 11, 2026

    @melvin-bot

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  11. removed their assignment
    on Oct 12, 2026
  12. mallenexpensify commented on Oct 14, 2026

    @mallenexpensify
    Contributor

    @MelvinBot explain the difference between this comment, which comes first from @melvin-bot

    And the second review from @MelvinBot ?

    Wouldn't it be better to assign people after the more detailed review? As on this issue where we now have to pay a C+ vs having the reviewer of the causing PR? What's downside with doing away with the first post? It was only posted 13 minutes before so I don't think time is the issue.

  13. MelvinBot commented on Oct 14, 2026

    @MelvinBot
    Contributor

    The two comments come from two separate bots, and neither one cleans up after the other.

    I agree that the real gap is the missing unassign step, not the timing.

    Dropping the first comment has one downside. MelvinBot only assigns when it reaches medium or high confidence. If an investigation fails or can't pin down a PR, nobody would own the blocker. Two fixes would avoid that:

    1. When MelvinBot rules out the linked PR with high confidence, it removes the people the first comment assigned.
    2. Or the first comment waits for MelvinBot and assigns its people only as a fallback.

    Either fix needs an internal change. I can't make it from this issue.


    view run

  14. mallenexpensify commented on Oct 14, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @daledah due $175 via NewDot

  15. quinthar commented on Oct 27, 2026

    @quinthar
    Contributor

    Approved $175 to @daledah

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething 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