Skip to content

[$175] Scheduled Submit - Retracting a report fails restore the workspace Scheduled Submit policy #104361

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.5.13-3
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/tc/TC-3464
Email or phone of affected tester (no customers): [email protected]
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: macOS 27.0.1 / Chrome 155, iOS 18.7.10 / iPhone XS
App Component: Money Requests

Action Performed:

  1. As Workspace Owner / Admin, navigate to Workspace Settings > Workflows
  2. Enable Scheduled Submit and set the cadence to twice a month
  3. As Employee, open or create an Open (unsubmitted) report in the workspace chat
  4. Verify the Next Steps section displays: "Waiting for your expenses to automatically submit on the 1st and 16th of each month." and there's no GBR on the LHN
  5. Manually Submit the report
  6. Retract the submitted report so it returns to an Open status
  7. Observe the "Next Steps" section and GBR in LHN

Expected Result:

After retracting the report back to Open status:

  • The "Next Steps" section should display: "Waiting for your expenses to automatically submit on the 1st and 16th of each month."
  • No manual green "Submit" button (GBR) should appear in the Left Hand Navigation

Actual Result:

Retracting the report breaks the Scheduled Submit state and reverts to a manual workflow. An optimistic UI flash briefly shows the correct auto-submit text before the final re-render overwrites it with "Waiting for you to submit expenses," while a "Submit" GBR incorrectly displays in the LHN.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

To be added by Applause Leads

Bug7288521_1792186978828.Schedule_submit_optimistic_UI_flash.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Chandana Kiran
Bug ID: 7288521

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @MonilBhavsar

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Oct 19, 2026
  2. MelvinBot commented on Oct 19, 2026

    @MelvinBot
    Contributor

    Proposal

    Summary: The final state ("Waiting for you to submit expenses" plus a Submit GBR) matches how the App and the server treat retracted reports today. The real App bug is the short flash of the auto-submit text. That flash comes from retractReport building the wrong optimistic next step.

    What is the root cause of that problem?

    1. The flash. retractReport calls buildOptimisticNextStep without isReopen: true (ReportWorkflow.ts:708-718). Because of that, it falls into the harvesting branch and shows "automatically submit on the 1st and 16th". reopenReport does pass isReopen: true (ReportWorkflow.ts:544-555), which gives WAITING_TO_SUBMIT (NextStepUtils.ts:171-180).
    2. The final text. The UI shows the stored report.nextStep. The RetractReport response replaces the optimistic value with WAITING_TO_SUBMIT.
    3. The GBR. shouldCurrentUserSubmitReport returns true for any report the user owns that was retracted or reopened, whatever policy.harvesting is set to (ReportUtils.ts:3670-3674). The Submit button logic follows the same rule (ReportSecondaryActionUtils.ts:288-297). This rule was added on purpose in Fix submit button color for reopened or retracted report and fix: add report owner check when computing outstanding state of chat …. It is not a recent regression.

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

    This depends on a product decision. An internal engineer needs to confirm whether backend Scheduled Submit picks up retracted reports.

    • If retracted reports need a manual submit (likely, since the server's next step says so): Pass isReopen: true to buildOptimisticNextStep in retractReport, the same way reopenReport does. This removes the flash. Add a retractReport unit test that checks the optimistic nextStep. The GBR and final text then stay as they are, by design.
    • If retracted reports should go back to Scheduled Submit: The backend must change the RetractReport next step and include retracted reports in harvesting. That change is outside the App repo, so escalate it to an internal engineer. After that, the App should apply the retracted/reopened override in shouldCurrentUserSubmitReport and ReportSecondaryActionUtils only when harvesting is off. An App-only change would conflict with the server's next step.
    Investigation details
    • hasReportBeenRetracted checks the report field first, then any RETRACTED report action. Once set, the flag stays for the life of the report (ReportUtils.ts:14575-14601).
    • retractReport successData only clears pending fields and never writes nextStep. The overwrite therefore comes from the server's update.
    • Submit button not shown on report preview after reopening closed report covered Instant Submit, where a reopened report "won't automatically submit". The fix applied the manual-submit rule to every retracted or reopened report, including Scheduled Submit workspaces.
    • The existing retractReport tests in tests/actions/IOUTest/ReportWorkflowTest.ts don't check nextStep.
    • I did not check backend harvesting behavior for retracted reports, so the product question above is still open.

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


    view run

  3. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Oct 19, 2026
  4. melvin-bot commented on Oct 19, 2026

    @melvin-bot

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

  5. changed the title [-]Scheduled Submit - Retracting a report fails restore the workspace Scheduled Submit policy[/-] [+][$175] Scheduled Submit - Retracting a report fails restore the workspace Scheduled Submit policy[/+] on Oct 19, 2026
  6. melvin-bot commented on Oct 19, 2026

    @melvin-bot
  7. camesenin commented on Oct 19, 2026

    @camesenin

    Proposal

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

    When retracting an expense report in a workspace configured with Scheduled Submit, an optimistic UI flash briefly displays auto-submit text before snapping to "Waiting for you to submit expenses" and rendering the green "Submit" button (GBR).

    What is the root cause of that problem?

    A mismatch between the optimistic NextStep builder and the server contract:

    1. The Optimistic Flash: In src/libs/actions/IOU/ReportWorkflow.ts:708-718, retractReport calls buildOptimisticNextStep:
      const optimisticNextStep = buildOptimisticNextStep({
          report: expenseReport,
          predictedNextStatus: CONST.REPORT.STATUS_NUM.OPEN,
          policy,
          rules,
          currentUserAccountIDParam,
          currentUserEmailParam,
          hasViolations,
          isASAPSubmitBetaEnabled,
          isTrackIntentUser,
      });
      Notice that retractReport omits isReopen: true. Without this flag, buildOptimisticNextStep treats the open report as a standard auto-submitting draft and falls into the harvesting branch, optimistically flashing the scheduled auto-submit banner.
    2. The Parity Contrast with reopenReport: In ReportWorkflow.ts:544-555, reopenReport explicitly passes isReopen: true, which directs NextStepUtils.ts:171-180 to evaluate WAITING_TO_SUBMIT:
      if (isReopen) {
          nextStep = {
              messageKey: shouldShowMarkAsDone({isTrackIntentUser, report, policy, rules})
                  ? CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_MARK_AS_DONE
                  : CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_SUBMIT,
              icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
              actorAccountID: ownerAccountID,
          };
          break;
      }
    3. The GBR & Server Parity: Retracted and reopened reports intentionally transition to manual submission by design (PR Fix submit button color for reopened or retracted report #66140 & PR fix: add report owner check when computing outstanding state of chat … #79893) so submitters can review and manually submit edits. The server returns WAITING_TO_SUBMIT. Because the optimistic step in retractReport generated the auto-submit message, the client experiences a jarring UI flash when the server response arrives and replaces it with WAITING_TO_SUBMIT.

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

    1. In src/libs/actions/IOU/ReportWorkflow.ts:
      In retractReport, pass isReopen: true (or expand the property to isReopenOrRetract) into buildOptimisticNextStep:
      const optimisticNextStep = buildOptimisticNextStep({
          report: expenseReport,
          predictedNextStatus: CONST.REPORT.STATUS_NUM.OPEN,
          policy,
          rules,
          currentUserAccountIDParam,
          currentUserEmailParam,
          hasViolations,
          isASAPSubmitBetaEnabled,
          isReopen: true,
          isTrackIntentUser,
      });
    2. In src/libs/NextStepUtils.ts:
      Update type definitions and jsdoc if desired to clarify that isReopen covers both reopened and retracted report lifecycles.

    This eliminates the optimistic flash at the source, ensuring seamless visual consistency from the exact moment the user confirms retraction.

    What alternative solutions did you explore? (Optional)

    Attempting to force retracted reports into auto-submit on the frontend alone. This was rejected because the backend contract for RetractReport explicitly emits WAITING_TO_SUBMIT, and frontend-only overrides would continuously conflict with incoming server state.

    Automated Tests

    In tests/actions/IOUTest/ReportWorkflowTest.ts:

    • Add a test for retractReport asserting that the generated optimistic nextStep matches CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_SUBMIT on a scheduled submit policy report, with zero UI flash.

    Contributor details
    Your Expensify account email: [email protected]
    Upwork Profile Link: https://www.upwork.com/freelancers/~0170d6b02e9440e604

  8. melvin-bot commented on Oct 19, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  9. khizzcr7 commented on Oct 19, 2026

    @khizzcr7

    Proposal

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

    In a workspace with Scheduled Submit (twice a month), manually submitting a report and then retracting it back to Open flashes the correct "Waiting for your expenses to automatically submit on the 1st and 16th of each month" next-step text briefly, then overwrites it with "Waiting for you to submit expenses" plus a manual Submit GBR in the LHN.

    What is the root cause of that problem?

    The flash is a genuine App bug: retractReport calls buildOptimisticNextStep without isReopen: true (ReportWorkflow.ts ~L708-718), while reopenReport passes it (~L544-555). Without the flag it falls into the harvesting branch and shows the auto-submit text optimistically; the server's RetractReport response then overwrites it with WAITING_TO_SUBMIT.

    The final GBR comes from shouldCurrentUserSubmitReport returning true for any report the user owns that was retracted or reopened regardless of policy.harvesting (ReportUtils.ts ~L3670-3674) - a deliberate rule added in PR #66140 / #79893, not a recent regression.

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

    • App-side fix for the flash: pass isReopen: true to buildOptimisticNextStep in retractReport, the same way reopenReport does. This removes the optimistic flash; nothing changes after the server response.
    • The final-state question (GBR + "waiting for you to submit") is a product decision: if retracted reports must return to Scheduled Submit, the backend RetractReport next step has to change first, and then the retracted/reopened override in shouldCurrentUserSubmitReport and ReportSecondaryActionUtils should only apply when harvesting is off.

    What will I verify?

    • Retract a report in a Scheduled Submit workspace: no auto-submit text flash before the server response.
    • Unit test on retractReport asserting the optimistic nextStep.
  10. sijo0218 commented on Oct 20, 2026

    @sijo0218

    Contributor details
    Your Expensify account email: [email protected]
    Upwork Profile Link: https://www.upwork.com/freelancers/~011091133bbb09cd1c

    Proposal

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

    In a workspace configured with Scheduled Submit (e.g., twice a month), when a user submits an expense report and then retracts it back to Open, the UI briefly flashes the scheduled auto-submit banner ("Waiting for your expenses to automatically submit on the 1st and 16th of each month") before immediately snapping to "Waiting for you to submit expenses" and rendering the green "Submit" button (GBR).

    What is the root cause of that problem?

    This is caused by a missing isReopen: true flag in the optimistic next-step builder for retracted reports, creating a mismatch between client optimistic state and the server response contract:

    1. The Optimistic Flash in retractReport (src/libs/actions/IOU/ReportWorkflow.ts:708-718):
      When retractReport executes, it builds the optimistic next step:

      const optimisticNextStep = buildOptimisticNextStep({
          report: expenseReport,
          predictedNextStatus: CONST.REPORT.STATUS_NUM.OPEN,
          policy,
          rules,
          currentUserAccountIDParam,
          currentUserEmailParam,
          hasViolations,
          isASAPSubmitBetaEnabled,
          isTrackIntentUser,
      });

      Notice that retractReport completely omits isReopen: true.
      In NextStepUtils.ts:171-180, without isReopen: true, buildOptimisticNextStep treats the report as a brand-new open expense report and falls through into the harvesting/scheduled-submit calculation branch, optimistically rendering the auto-submit schedule banner.

    2. The Contrast with reopenReport (ReportWorkflow.ts:544-555):
      In reopenReport, isReopen: true is explicitly supplied:

      const optimisticNextStep = buildOptimisticNextStep({
          ...
          isReopen: true,
          isTrackIntentUser,
      });

      In NextStepUtils.ts:171-180, when isReopen is true, the next step immediately assigns:

      if (isReopen) {
          nextStep = {
              messageKey: shouldShowMarkAsDone({isTrackIntentUser, report, policy, rules})
                  ? CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_MARK_AS_DONE
                  : CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_SUBMIT,
              icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
              actorAccountID: ownerAccountID,
          };
          break;
      }
    3. Backend Contract and GBR Design:
      Per PR Fix submit button color for reopened or retracted report #66140 and PR fix: add report owner check when computing outstanding state of chat … #79893, both retracted and reopened reports intentionally pause automatic harvesting so that the submitter can review their adjustments and manually submit. The server-side RetractReport command explicitly emits nextStep: WAITING_TO_SUBMIT, and shouldCurrentUserSubmitReport (src/libs/ReportUtils.ts:3670-3674) returns true whenever hasReportBeenRetracted(report) || isReportReopened(report).
      Because retractReport optimistically showed the scheduled auto-submit text, when the server response arrives a moment later with WAITING_TO_SUBMIT, it causes a jarring UI flash and shifts the banner.

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

    1. Pass isReopen: true in retractReport (src/libs/actions/IOU/ReportWorkflow.ts:716):
      Align retractReport with reopenReport by passing isReopen: true to buildOptimisticNextStep:

      --- a/src/libs/actions/IOU/ReportWorkflow.ts
      +++ b/src/libs/actions/IOU/ReportWorkflow.ts
      @@ -714,6 +714,7 @@ function retractReport(
               currentUserEmailParam,
               hasViolations,
               isASAPSubmitBetaEnabled,
      +        isReopen: true,
               isTrackIntentUser,
           });
    2. This guarantees that the optimistic update immediately outputs CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_SUBMIT, matching the server response from the exact moment the user clicks Retract and completely eliminating the UI flash.

    What alternative solutions did you explore? (Optional)

    • Modifying shouldCurrentUserSubmitReport to suppress GBR on scheduled submit workspaces: Rejected because retracted reports intentionally require manual re-submission by design (allowing users to edit/add expenses without premature auto-harvesting).

    Automated Tests

    In tests/actions/IOUTest/ReportWorkflowTest.ts:

    • Add a unit test for retractReport asserting that on a workspace with scheduled submit (autoReportingFrequency: TWICE_A_MONTH), the returned optimistic nextStep.messageKey equals CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_SUBMIT and does not flash the harvesting schedule.
  11. melvin-bot commented on Oct 20, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  12. emkhalid commented on Oct 20, 2026

    @emkhalid
    Contributor

    @Ollyws I reproduced this issue and tested the applied fix from Melvin’s proposal. While Melvin's 1-line change removes the visual flash, it does not actually fix the core bug reported in this issue. Here is why:

    The Issue vs. Melvin’s Solution

    • What this issue expects: Retracting a report should restore the Scheduled Submit policy—the report should display the auto-submit schedule ("Waiting for your expenses to automatically submit on the 1st and 16th...") and show no green "Submit" button.
    • What Melvin's proposal actually does: It only hides the visual UI flash by forcing the report into a manual submission workflow ("Waiting for you to submit expenses" + green "Submit" button). It does not restore Scheduled Submit.

    The Technical Reality (Network Verified)

    We inspected the live network response during retraction. The backend RetractReport command explicitly returns nextStep: "waitingToSubmit" and pauses auto-harvesting.

    Screencast.From.2026-10-20.11-27-33.webm

    Because of this:

    1. Frontend-only fixes cannot restore Scheduled Submit: Any frontend-only attempt to restore auto-submit will immediately be overwritten by the server response.
    2. True fix requires Backend (BE): To actually achieve the issue's expected result, the backend team must update RetractReport to restore harvesting rather than requiring manual resubmission.

    Demos

    • Reproduction Video:
    Reprodemo104361.webm
    • **Melvin's Fix Video **
    melvinFix104361.webm

    Would you like us to submit a full formal proposal outlining both decision paths (Backend requirement to restore auto-submit vs. FE-only flash mitigation), or would you prefer to escalate this to an internal engineer first for backend confirmation?

  13. dhaulagani commented on Oct 20, 2026

    @dhaulagani

    Contributor details
    Your Expensify account email: [email protected]
    Upwork Profile Link: https://www.upwork.com/freelancers/~017b76638cdbe0f8d6

    Proposal

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

    In a workspace configured with Scheduled Submit (e.g. twice a month), when a user retracts a previously submitted expense report back to Open, the UI optimistically flashes the scheduled auto-submit text ("Waiting for your expenses to automatically submit on the 1st and 16th of each month") before the server response settles on "Waiting for you to submit expenses" and renders the green "Submit" button (GBR).

    What is the root cause of that problem?

    A divergence between the optimistic NextStep builder and the server contract:

    1. Frontend Optimistic Divergence (src/libs/actions/IOU/ReportWorkflow.ts:retractReport):
      retractReport calls buildOptimisticNextStep with predictedNextStatus: CONST.REPORT.STATUS_NUM.OPEN, but does not pass an explicit intent flag indicating this is a retracted report. In src/libs/NextStepUtils.ts, the case CONST.REPORT.STATUS_NUM.OPEN evaluates if (policy?.harvesting?.enabled && hasTransactions), resolving to WAITING_FOR_AUTOMATIC_SUBMIT.

    2. Server Contract Intent (RetractReport API):
      When an expense report is explicitly retracted by the submitter, the backend halts automatic harvesting on that report to avoid immediately re-submitting expenses that the user intentionally pulled back to edit/adjust, returning nextStep: "waitingToSubmit".

    Because the optimistic update assumes automated harvesting while the server enforces manual resubmission, a visual flash / layout shift occurs upon receiving the server response.

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

    Primary Architecture Solution (Contract Alignment & Flash Elimination):

    1. In src/libs/actions/IOU/ReportWorkflow.ts, pass isRetracted: true into buildOptimisticNextStep parameters inside retractReport:
    const optimisticNextStep = buildOptimisticNextStep({
        report: expenseReport,
        predictedNextStatus: CONST.REPORT.STATUS_NUM.OPEN,
        policy,
        rules,
        currentUserAccountIDParam,
        currentUserEmailParam,
        hasViolations,
        isASAPSubmitBetaEnabled,
        isTrackIntentUser,
        isRetracted: true,
    });
    1. In src/libs/NextStepUtils.ts, add isRetracted to BuildNextStepNewParams and handle it in case CONST.REPORT.STATUS_NUM.OPEN:
    if (isReopen || isRetracted) {
        nextStep = {
            messageKey: shouldShowMarkAsDone({isTrackIntentUser, report, policy, rules})
                ? CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_MARK_AS_DONE
                : CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_SUBMIT,
            icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
            actorAccountID: ownerAccountID,
        };
        break;
    }

    This guarantees 100% synchronization between frontend optimistic state and server payload, eliminating the UI flash completely without regressions.

    Alternative / Product Escalation Path:

    If Expensify Product determines that retracted reports should resume automatic harvesting on future scheduled cycles without manual user intervention, that change requires a backend update to the RetractReport command contract. In that event, the frontend can easily omit isRetracted once the BE change lands. Until such a product shift is confirmed, aligning the optimistic state with the current server contract is the correct, regression-free implementation.

    What alternative solutions did you explore? (Optional)

    • Setting isReopen: true directly in retractReport (proposed by MelvinBot): while functionally similar, semantically conflates report reopen (unapproving/rejecting by manager) with submitter retraction. Introducing an explicit isRetracted property preserves clean domain separation and avoids unintended side effects in downstream analytics/guards.
  14. melvin-bot commented on Oct 20, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  15. Ollyws commented on Oct 20, 2026

    @Ollyws
    Contributor

    The question here is whether we restore scheduled submit after the report is retracted, if not Melvin's proposal looks good, otherwise this will require some backend work.

    🎀👀🎀 C+ reviewed

  16. melvin-bot commented on Oct 20, 2026

    @melvin-bot

    Triggered auto assignment to @MonilBhavsar, see https://stackoverflow.com/c/expensify/questions/7972 for more details.

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 contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions