Skip to content

Fix scan-failed expense sheet showing not found after reconnect by re… - #98863

Open
yusufdeveloper2903 wants to merge 3 commits into
Expensify:mainfrom
yusufdeveloper2903:fix/96257-scan-failed-expense-not-moved-when-paid-offline
Open

Fix scan-failed expense sheet showing not found after reconnect by re…#98863
yusufdeveloper2903 wants to merge 3 commits into
Expensify:mainfrom
yusufdeveloper2903:fix/96257-scan-failed-expense-not-moved-when-paid-offline

Conversation

@yusufdeveloper2903

@yusufdeveloper2903 yusufdeveloper2903 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

When a scan-failed expense is optimistically moved to a new report on Pay (#97084), the backend creates its own report instead of reusing optimisticHoldReportID, so the optimistic report is dropped in successData on reconnect. If the user opened the moved expense while offline, its sheet stayed pointed at the dropped optimistic report and its client-only copied action, so on reconnect the sheet showed the "Hmm... it's not here" page instead of the expense.

This PR adds watchMovedScanFailedTransactions: after Pay is sent, it watches each moved transaction until the backend assigns its real report ID, then re-points the expense thread (parentReportID / parentReportActionID / chatReportID) to the backend report and its real IOU action, and redirects any route still showing the optimistic report to the real one. The reconciliation also handles the failure path (transaction returning to its original report).

Known limitation: the watcher lives in memory, so if the app is reloaded between the offline Pay and reconnect, the reconciliation does not run. Fully closing that edge requires the backend to reuse optimisticHoldReportID.

Fixed Issues

$ #96257
$ #98821
PROPOSAL: #96257 (comment)

Tests

Precondition: workspace with payments enabled, a report containing one valid manual expense and one scan-failed expense (amount 0, missing merchant), approved and ready to pay.

  1. Open the report and go offline.
  2. Click Mark as paid. Verify the scan-failed expense moves to a new Draft report in the workspace chat.
  3. Open the new Draft report (the sheet shows the scan-failed expense details).
  4. Go online while the sheet is open.
  5. Verify the sheet keeps showing the expense data, the URL switches to the backend's report, and the "Hmm... it's not here" page never appears.
  • Verify that no errors appear in the JS console

Offline tests

  1. Steps 1–3 above.
  2. Verify the sheet shows the expense data while offline.
  3. Go online and verify step 5 above.

QA Steps

  1. Go to a workspace chat and create a manual expense and a scan expense with an invalid receipt; wait for the scan to fail.
  2. Submit and approve the report, then go offline.
  3. Click Mark as paid and open the new report containing the scan-failed expense.
  4. Go online.
  5. Verify the report keeps showing the expense data and no "Hmm... it's not here" page appears.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari After fix:
REC-20260818163046.mp4

…conciling the moved thread and route with the backend report
@yusufdeveloper2903
yusufdeveloper2903 requested review from a team as code owners August 18, 2026 11:44
@melvin-bot
melvin-bot Bot requested review from brunovjk and garrettmknight and removed request for a team August 18, 2026 11:44
@melvin-bot

melvin-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

@brunovjk Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@github-actions

Copy link
Copy Markdown
Contributor

This PR adds a new Onyx.connectWithoutView call, so I've requested a review from the Onyx performance reviewers (@tgolen, @mountiny, @luacmartins) — a review from any one of them is enough. Please add a link in your PR description to the Slack discussion where the @frontend-performance team approved using connectWithoutView here.

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/libs/actions/IOU/PayMoneyRequest.ts 81.09% <100.00%> (+0.28%) ⬆️
src/libs/actions/IOU/Hold.ts 93.51% <96.00%> (+0.58%) ⬆️
... and 82 files with indirect coverage changes

Comment thread src/libs/actions/IOU/PayMoneyRequest.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab945f7bdb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/libs/actions/IOU/Hold.ts Outdated
…e params instead of substring before redirecting
};
}

export {getReportFromHoldRequestsOnyxData, putOnHold, putTransactionsOnHold, unholdRequest};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB: Do we need to add a JSDoc comment here? Thanks.

},
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB: Same here, I think a comment is missing.

@brunovjk

Copy link
Copy Markdown
Contributor

@yusufdeveloper2903 I think I found a bug:

  • Go to workspace chat.
  • Create a manual expense.
  • Create a scan expense with invalid receipt.
  • Wait for smartscan to fail.
  • Open the report.
  • Click Submit, Approve.
  • Go offline.
  • Mark as paid.
  • Open the new report containing the scan expense.
  • Open report details side bar
  • Go online.
    Result (bug): The new report containing the scan expense shows "not here" page after returning online.
Screen.Recording.2026-08-18.at.10.40.44.mov

…ument the moved scan-failed reconciliation helpers
@yusufdeveloper2903

Copy link
Copy Markdown
Contributor Author

@yusufdeveloper2903 I think I found a bug:

Thanks for the catch Fixed now every route referencing the optimistic report gets repointed, not just the focused one (JSDoc comments added too). Here's a video following your repro steps , could you take another look Thanks

REC-20260818201640.mp4

@garrettmknight garrettmknight left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flow makes sense from a product perspective.

Comment on lines +1097 to +1098
// The backend's report actions for the moved expense may arrive after the transaction update, and this runs from the
// action layer where no view exists to subscribe with useOnyx, so connectWithoutView is the only way to wait for them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all triggered from payMoneyRequest() which is called from a UI component, so can't the data be gathered there with useOnyx() and passed through parameters?

@Beamanator

Copy link
Copy Markdown
Contributor

Bump @yusufdeveloper2903 please prioritize this since it's fixing a deploy blocker

@deetergp

Copy link
Copy Markdown
Contributor

Heads up: we are going to revert PR 97084 to clear the deploy blocker rather than fix forward here.

Revert PR: #98914

The reasoning is deploy timing, not the quality of this work. Issue 98821 is the last unchecked blocker on the 2026-08-18 deploy checklist, the regression is not in production yet, and the revert restores exactly what production runs today.

That puts issue 96257 back on the board at Weekly priority, so the optimistic split can be re-landed without deadline pressure.

@yusufdeveloper2903 thank you for the quick turnaround here, and sorry to send it back. One thing worth considering for the next attempt: the App already reconciles "the backend used a different report than the optimistic one" through preexistingReportID, handled by the HandleUnusedOptimisticID middleware and replaceOptimisticReportWithActualReport.ts. If the backend returns preexistingReportID for the moved expense, or reuses optimisticHoldReportID, that existing path handles the redirect and the childReportID fixup — and it survives an app reload, which the in-memory watcher here cannot.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants