Skip to content

[Exploratory] Web - Workspace - Transfer owner lead to not here page #101019

Open

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: v9.4.77-0
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/53678249/test-cases/41237966?sort=%2Bidentifier
Email or phone of affected tester (no customers): leungbratt6+12093@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Windows 11/Chrome
App Component: Workspace Settings

Action Performed:

Preconditions: Owner has invited another user as Admin in Workspace

  1. As the Admin, open the New Dot app
  2. Navigate to Workspaces tab
  3. Click on the three-dot menu of the Owner workspace - Transfer Owner - Continue

Expected Result:

No not here page

Actual Result:

Not here page appear

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7257642_1789181189769.tranfer.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Julia Ponikarchuk
Bug ID: 7257642

Issue OwnerCurrent Issue Owner: @abdulrahuman5196

Activity

  1. applause-bot commented on Sep 12, 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/3eef47e8be4e2a596b5b0e0b87535abc27fa33475b254c1155841405d673087f

  2. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 24, 2026
  3. MelvinBot commented on Sep 24, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    The "Not here" page is the success screen of the transfer-owner flow, not the check screen. DynamicWorkspaceOwnerChangeSuccessPage.tsx:41-42 latches its access decision into a useRef on the very first render:

    const policy = usePolicy(policyID);
    const shouldShowRef = useRef(!policy?.errorFields?.changeOwner && policy?.isChangeOwnerSuccessful);

    useRef runs its initializer once and never re-evaluates. usePolicy is a bare useOnyx, and useOnyx deliberately returns undefined on a first connection when a merge is already queued for that key. So if the page mounts while policy_<id> has a pending merge, shouldShowRef.current is undefined forever, shouldBeBlocked is permanently true at line 53, and AccessOrNotFoundWrapper.tsx:227-228 renders NotFoundPage. It never recovers once the data lands.

    The Workspaces-list entry point makes that race much more likely, because it writes to policy_<id> more than the other entry points do. Every requestWorkspaceOwnerChange call optimistically merges {errorFields: null, isLoading: true, isChangeOwnerSuccessful: false, isChangeOwnerFailed: false} (Member.ts:770-781) — i.e. it re-nulls exactly the two fields the success page latches on. From the Workspaces list, one flow fires it three times: TransferOwnershipFlow.tsx:44, DynamicWorkspaceOwnerChangeWrapperPage.tsx:56, and again on Continue at WorkspaceOwnerChangeCheck.tsx:74 — plus a clearWorkspaceOwnerChangeFlow merge. The Member details entry point fires it twice. The wrapper then navigates to the success route with forceReplace at line 71, mounting the success page right inside that merge window.

    DynamicWorkspaceOwnerChangeErrorPage.tsx:46-47 has the identical defect.

    What changes do you think we should make in order to solve the problem?

    1. Make the latch sticky-true instead of sticky-first-render, in both pages, so an undefined first render can't poison it:

      • DynamicWorkspaceOwnerChangeSuccessPage.tsx:42 — initialize the ref to false and OR the derived condition into it on every render.
      • DynamicWorkspaceOwnerChangeErrorPage.tsx:47 — same treatment for !policy?.errorFields && policy?.isChangeOwnerFailed.

      Keeping a ref (rather than a plain derived value) is still needed: closePage calls clearWorkspaceOwnerChangeFlow, which sets isChangeOwnerSuccessful: false and would otherwise flash "Not here" on the way out.

    2. Remove the duplicate requestWorkspaceOwnerChange at TransferOwnershipFlow.tsx:44. The wrapper page already fires it on mount, so the list entry point sends the command twice. This mirrors WorkspaceMemberDetailsPage.tsx:318-321, which only clears and navigates, and it narrows the race window.

    What alternative solutions did you explore? (Optional)

    Hypotheses that were tested and ruled out are in the details below.

    Investigation details — what was ruled out, and how

    Findings below were verified by executing the real navigation and access code in scratch Jest tests against this checkout (no repo files modified).

    Rendering the real success page with Onyx seeded:

    Scenario Result
    Policy settled in Onyx before mount renders normally
    Mount while an Onyx.merge on policy_<id> is pending NotFoundPage
    Same, after that merge flushes (data now correct) NotFoundPage — never recovers
    Policy key absent at mount, arrives later NotFoundPage — never recovers

    The error page behaves identically. mergeQueue[key] is created synchronously inside Onyx.merge and cleared only after an async get(), so any useOnyx connection mounting in that window reads undefined.

    Ruled out:

    • Routing / entryScreens. workspaces/change-owner/<policyID>/<accountID>/<error>/owner-change-success resolves correctly through getStateFromPath and getAdaptedStateFromPath, passes entry-screen validation, and policyID is populated on the success screen (inherited via getStateForDynamicRoute). Same for the error route and for the Member-details base path.
    • The hard-coded 'amountOwed' at TransferOwnershipFlow.tsx:47. It is a real CONST.POLICY.OWNERSHIP_ERRORS value, nothing validates the :error segment at runtime, and all three entry points hard-code the same placeholder. The wrapper overwrites it via Navigation.setParams once the API responds. Worst case it renders a momentarily blank check step, not a 404.
    • shouldBeBlocked on the check page. On web both platform shims are no-ops (shouldRenderTransferOwnerButton and shouldShowChangeWorkspaceOwnerPage both return true), so the check page cannot 404 on web. This matches the report: the check page rendered and the tester pressed its button.
    • Policy access. An admin who is not the owner passes canEditWorkspaceSettings and isGroupPolicy on an active or archived Collect/Control workspace; canBeAccessedIfArchived is already set.

    Caveat: the failing mount was reproduced deterministically in a seeded render, but the production timing itself was not reproduced live — it is a race, which fits a bug that appears for some users and workspaces and not others. A one-shot confirmation: log policy at the first render of DynamicWorkspaceOwnerChangeSuccessPage during the repro; if it is undefined while Onyx holds the policy, that is the whole bug.

    Secondary issue, worth fixing alongside: TransferOwnershipFlow.tsx:47 passes the current user's accountID where WorkspaceMemberDetailsPage.tsx:320 passes the owner's accountID. That param feeds the personal-details lookup and the go-back target in WorkspaceOwnerChangeCheck.tsx, so from the Workspaces list those resolve to the wrong person.

    Possibly related issues

    • Unable to take over ownership. — also a failing ownership transfer, but the symptom there is a server-side error rather than a "Not here" page. Likely a separate problem.

    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/101019) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Sep 24, 2026
  5. melvin-bot commented on Sep 24, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @abdulrahuman5196 (External)

  6. melvin-bot commented on Sep 24, 2026

    @melvin-bot

    Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.

  7. ashusatyarthi-wq commented on Sep 26, 2026

    @ashusatyarthi-wq
    Contributor details Your Expensify account email: ashu.satyarthi.business@gmail.com Upwork Profile Link: https://www.upwork.com/freelancers/~01b8ab177d61dc225c

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When transferring ownership of a workspace from the Workspaces tab three-dot menu, completing the transfer causes a "Not here" (404) page to appear instead of the ownership change confirmation screen.

    What is the root cause of that problem?

    In src/pages/workspace/members/DynamicWorkspaceOwnerChangeSuccessPage.tsx:41-42:

    const policy = usePolicy(policyID);
    const shouldShowRef = useRef(!policy?.errorFields?.changeOwner && policy?.isChangeOwnerSuccessful);

    useRef initializes on the very first mount and never re-evaluates.

    1. When navigating to the success page via forceReplace right after completing the transfer, policy_<id> is in the middle of active Onyx merges (requestWorkspaceOwnerChange / clearWorkspaceOwnerChangeFlow).
    2. When a key has an active merge in progress, usePolicy (useOnyx) initial connection returns undefined.
    3. Consequently, shouldShowRef.current evaluates to false on the initial mount and stays permanently false.
    4. At line 53, shouldBeBlocked={!shouldShowRef.current} becomes permanently true, causing AccessOrNotFoundWrapper to render NotFoundPage ("Not here" page).
    5. DynamicWorkspaceOwnerChangeErrorPage.tsx:47 suffers from the identical issue (shouldShowRef = useRef(!policy?.errorFields && policy?.isChangeOwnerFailed)).
    6. Additionally, in TransferOwnershipFlow.tsx:44, requestWorkspaceOwnerChange is redundantly invoked prior to navigation, while DynamicWorkspaceOwnerChangeWrapperPage already triggers it on mount, causing duplicate server commands and extending the merge window.

    What changes do you think we should make in order to solve the problem?

    1. Make shouldShowRef sticky-true in DynamicWorkspaceOwnerChangeSuccessPage.tsx and DynamicWorkspaceOwnerChangeErrorPage.tsx:
      In DynamicWorkspaceOwnerChangeSuccessPage.tsx:

      const policy = usePolicy(policyID);
      const isSuccess = !policy?.errorFields?.changeOwner && !!policy?.isChangeOwnerSuccessful;
      const shouldShowRef = useRef(isSuccess);
      if (isSuccess) {
          shouldShowRef.current = true;
      }

      (Retaining the ref ensures that when closePage calls clearWorkspaceOwnerChangeFlow to reset isChangeOwnerSuccessful: false, the screen does not flash "Not here" while animating out).

      In DynamicWorkspaceOwnerChangeErrorPage.tsx:

      const policy = usePolicy(policyID);
      const isFailed = !policy?.errorFields && !!policy?.isChangeOwnerFailed;
      const shouldShowRef = useRef(isFailed);
      if (isFailed) {
          shouldShowRef.current = true;
      }
    2. Deduplicate requestWorkspaceOwnerChange in TransferOwnershipFlow.tsx:
      Remove line 44 (requestWorkspaceOwnerChange(...)) from TransferOwnershipFlow.tsx, matching WorkspaceMemberDetailsPage.tsx:318-321 which clears and navigates, letting the wrapper page manage the API request on mount. Also pass the workspace owner's accountID (policy?.ownerAccountID ?? currentUserPersonalDetails.accountID) so the back-path parameter correctly resolves to the former owner.

    What alternative solutions did you explore? (Optional)

    Using a purely dynamic boolean without a ref was considered, but closePage() immediately invokes clearWorkspaceOwnerChangeFlow(policyID) which clears isChangeOwnerSuccessful before navigation unmounts, creating an exit flash. A sticky-true latch guarantees that once success/error is verified from Onyx data, the page remains accessible through transition and dismissal.

  8. 15 remaining items

  9. melvin-bot commented on Oct 22, 2026

    @melvin-bot

    This issue has not been updated in over 14 days. @abdulrahuman5196 eroding to Weekly issue.

  10. melvin-bot commented on Nov 3, 2026

    @melvin-bot

    @abdulrahuman5196 Huh... This is 4 days overdue. Who can take care of this?

  11. melvin-bot commented on Nov 3, 2026

    @melvin-bot

    @abdulrahuman5196 Huh... This is 4 days overdue. Who can take care of this?

  12. mukhrr commented on Nov 3, 2026

    @mukhrr
    Contributor

    I think this is now fixed by #100501

  13. melvin-bot commented on Nov 3, 2026

    @melvin-bot

    @abdulrahuman5196 Huh... This is 4 days overdue. Who can take care of this?

  14. melvin-bot commented on Nov 4, 2026

    @melvin-bot

    @abdulrahuman5196 Eep! 4 days overdue now. Issues have feelings too...

  15. melvin-bot commented on Nov 5, 2026

    @melvin-bot

    @abdulrahuman5196 6 days overdue. This is scarier than being forced to listen to Vogon poetry!

  16. jeffrey-graham commented on Nov 7, 2026

    @jeffrey-graham
  17. melvin-bot commented on Nov 7, 2026

    @melvin-bot
  18. melvin-bot commented on Nov 9, 2026

    @melvin-bot

    @abdulrahuman5196 10 days overdue. Is anyone even seeing these? Hello?

  19. melvin-bot commented on Nov 11, 2026

    @melvin-bot

    @abdulrahuman5196 12 days overdue. Walking. Toward. The. Light...

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

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorNot a priorityOverdue

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions