Skip to content

[Due for payment 2026-10-05] Copilot switch race fires OpenApp with no authToken, landing users on "Hmm... it's not here" #101940

Closed

Description

@melvin-bot

Problem

When a user copilots into another account on New Expensify, the app can land on the "Hmm... it's not here" empty state (or get signed out with "Your session has expired") instead of the delegate's account.

The copilot switch itself succeeds. The failure is a race in NewDot that fires an OpenApp with no authToken, which the API rejects as an expired session.

Observed sequence

From a reported case (times relative to the switch):

  1. ConnectAsDelegate succeeds, then OpenApp as the delegate succeeds — the copilot session is live.
  2. ~55s later, a queued ReconnectApp is canceled mid-transition (signal is aborted without reason), so its finallyData never clears isLoadingApp.
  3. Milliseconds later, the recovery hook logs [Onyx] isLoadingApp missing after app is ready and fires its own openApp().
  4. Two OpenApp calls go out with no authToken at all. The API returns Missing authToken → AuthTokenExpired, "Your session has expired."
  5. On retry, the app logs [Delegate] Missing delegate field after switch, then Redirecting to Sign In because signOut() was called.

Root cause (proposed)

If that recovery openApp() lands inside the delegate-switch window, the request is built before the delegate token is installed, so it goes out with no authToken.

Scope

Not specific to one account. [Delegate] Missing delegate field after switch fires roughly 1,000–1,400 times a day across all users (about 7,800 occurrences over the week of 2026-09-10 to 2026-09-17), so this is a general copilot-switch race.

Fix direction — open question

Two views so far:

  • Guard the recovery path: have Delegate.connect() mark the transition so useIsLoadingAppRecovery skips it, and make sure the aborted ReconnectApp still clears isLoadingApp.
  • Counterpoint (adhorodyski): that reads as a patch over a real gap. Credentials should already be resolved by this point and delegate access is designed to be sorted out earlier. Skipping in useIsLoadingAppRecovery limits the blast radius but may not be the real fix.

Existing work in flight:

Possibly related issues

Mirrored from an internal issue so it is publicly visible to contributors and experts. Account identifiers and internal log references were intentionally left out.

Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. adhorodyski commented on Sep 23, 2026

    @adhorodyski
    Contributor

    I have also explored callstack-internal#70 that could be retargeted on this main.

  2. VickyStash commented on Sep 23, 2026

    @VickyStash
    Contributor

    Hi there, I'm helping @adhorodyski here 👋

  3. VickyStash commented on Sep 23, 2026

    @VickyStash
    Contributor

    Update:
    I've re-watched the FS sessions of the affected user and noticed one thing:
    User A tries to co-pilot to access another account B-> From account B sees Not Found page -> and then tries to access back account A using the transition link from classic and lands back to account A successfully.

    FS sessions: switching + not found -> noth found + user doing transition to retun to account A -> sucessfully landed on account A (and then all over again).

    After a closer look, it turned out the Hmm... it's not here page is not caused by the OpenApp 407 / missing authToken(these logs are coming from user transitioning user triggered in his own).
    The root cause of Not found page comes from MigratedUserWelcomeModalGuard!

    When the copilot switch finishes loading, the guard proactively navigates to the migrated user welcome modal by appending the migrated-user-welcome dynamic suffix to the current route. The user was on /settings/wallet, which is not in the modal's entryScreens, so getStateFromPath resolved the path to NOT_FOUND.

    App/src/ROUTES.ts

    Lines 251 to 253 in bf1662b

    MIGRATED_USER_WELCOME: {
    path: 'migrated-user-welcome',
    entryScreens: [SCREENS.HOME, SCREENS.INBOX, SCREENS.REPORT, SCREENS.SEARCH.ROOT, SCREENS.WORKSPACES_LIST, SCREENS.WORKSPACE.PROFILE, SCREENS.SETTINGS.ROOT],

    VL logs also proves it:

    Image
    I've tried locally to force co-pilot to show migrated welcome modal, and got the same Not Found page
    not-found-example.mp4

    I've just found that there is a PR #100859 that will change it (cc @chrispader). This PR checks whether the active screen can host their dynamic modal. If it cannot, the helper falls back to Home instead of constructing an invalid route. Which means if co-pilot should show the migrated modal, the user will be re-navigated to the Home page and the modal will be shown.

    @brianlee-expensify @mountiny But should the migrated user modal be shown while copilot access at all? If so, then the fix above should be enough. If no, I can update the check to not show the migrated user modal for copilot access.

  4. adhorodyski commented on Sep 23, 2026

    @adhorodyski
    Contributor

    #100859 alone will result in this case in being transitioned to Home + having a modal shown

  5. adhorodyski commented on Sep 23, 2026

    @adhorodyski
    Contributor

    I cleared up the drafts and think #101882 would be a nice telemetry followup to distinguish different sign out causes in logs. It'd skip me a lot of rounds here. @brianlee-expensify should I link to this issue too and open for review?

  6. brianlee-expensify commented on Sep 23, 2026

    @brianlee-expensify
    Contributor

    @VickyStash @adhorodyski will defer to @mountiny for your two questions above!

    @adhorodyski shall we close out this GH for now?

  7. adhorodyski commented on Sep 23, 2026

    @adhorodyski
    Contributor

    Yes, let's track this here only.

  8. mountiny commented on Sep 23, 2026

    @mountiny
    Contributor

    @adhorodyski @VickyStash great digging!

    I think that the migrated modal does not have to show for the copiloted flow

  9. VickyStash commented on Sep 24, 2026

    @VickyStash
    Contributor

    Here is the PR: #102124

  10. 6 remaining items

  11. changed the title [-]Copilot switch race fires OpenApp with no authToken, landing users on "Hmm... it's not here"[/-] [+][Due for payment 2026-10-05] Copilot switch race fires OpenApp with no authToken, landing users on "Hmm... it's not here"[/+] on Sep 28, 2026
  12. melvin-bot commented on Sep 28, 2026

    @melvin-bot
    Author

    @suneox

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.94-0 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-10-05. 🎊

    The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ 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]. If no checklist is needed for this issue, you can click: [no checklist button]

  13. melvin-bot commented on Oct 1, 2026

    @melvin-bot
    Author

    @suneox Whoops! This issue is 2 days overdue. Let's get this updated quick!

  14. melvin-bot commented on Oct 4, 2026

    @melvin-bot
    Author

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  15. melvin-bot commented on Oct 4, 2026

    @melvin-bot
    Author

    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
  16. suneox commented on Oct 5, 2026

    @suneox
    Contributor

    Contributor+ 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.

      The offending PR could not found, it's an edge case when migrate dynamic route that is difficult to reproduce with a specific data

    • [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.

      N/A

    • [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.

      N/A: It isn't easy to test for this bug per this comment

  17. mallenexpensify commented on Oct 7, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @suneox due $175 via NewDot

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 productionDailyKSv2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions