Skip to content

fix(purchases): a late execute response re-enables Execute in a newer review modal #1724

Closed as not planned

Description

@cristim

Summary

Follow-up from the review of #1718. In handleExecutePurchase (frontend/src/app.ts, the finally block at ~:622), the Execute button state is always restored when the request settles. That includes the case where the user closed the review modal and opened a new purchase while the request was in flight. #1718 already keeps a late failure from closing or clearing the newer modal: it checks the modal open token. But the finally still re-enables Execute on the new modal, using the old request's submitting state.

Steps

  1. Start a direct execute and keep the request pending.
  2. Close the review modal and open a new purchase.
  3. Let the first request fail with a response a purchase may have followed (5xx, 409, or one that carries execution_id).

Expected

The finally only touches the button for the modal session that sent the request (the same open-token check as the catch branch). A newer modal keeps its own button state.

Notes

Low severity: the newer modal's own selection decides what a click sends. Related: #1694, #1718.

Activity

  1. cristim commented on Nov 4, 2026

    @cristim
    MemberAuthor

    Checked before fixing, and I think the premise doesn't hold; the proposed fix would introduce a stuck button. (claude-ui-fix-2)

    • Only one request at a time. While a request is in flight, #execute-purchase-btn carries data-submitting=true. Opening a new single or fan-out modal keeps it (resetExecuteModeAndLabel and the fan-out summary in recommendations.ts skip the label/disabled update while submitting). So the newer modal can't start its own request until the old one settles.
    • The old request's finally is the only thing that clears that flag. setExecutePurchaseSubmitting(…, false, …) then recomputes disabled from the current selection (getPurchaseModalRecommendations() or the fan-out buckets) and the label from the current mode. So it applies the newer modal's own state, not the old request's.
    • Probe (a jest test inside purchase-execution-toast.test.ts, run on main and then with the proposed if (getPurchaseModalSession() === modalSession) guard around the finally): start a request, switch the session to a newer modal, then reject with a 502.
    in flight after the late failure
    main disabled, "Sending..." enabled, "Send for Approval" (the newer modal's label and state)
    with the guard disabled, "Sending..." still disabled, "Sending..."; nothing would ever clear it

    So what happens today is correct, and the session check belongs only on the close/clear branch, where #1718 already has it.

    I suggest closing this as not planned. Alternatively, rephrase it as "pin the current behaviour with a regression test"; the probe above would be that test, and I can open it as a small test-only PR if wanted. I'm leaving the close to the issue's owner.

  2. cristim commented on Nov 4, 2026

    @cristim
    MemberAuthor

    Closing as not planned (coordinator decision): main already restores the newer modal's Execute button correctly, and the proposed guard would leave it stuck on 'Sending...'. See the probe in the comment above.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions