Skip to content

OpenConceptLab/ocl_issues#2830 | stop Auto Match when leaving the project - #114

Merged
snyaggarwal merged 5 commits into
mainfrom
issues#2830
Oct 27, 2026
Merged

snyaggarwal merged 5 commits into
mainfrom
issues#2830

Conversation

@snyaggarwal

@snyaggarwal snyaggarwal commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes OpenConceptLab/ocl_issues#2830, together with OpenConceptLab/oclapi2#960: leaving a project while Auto Match runs no longer leaves the run going.

Changes

  • Leave dialog: while a run is going, in-app navigation opens "Auto Match is running" with Stay and Stop and leave. The project's own navigation after a save still goes through. Closing or reloading the tab shows the browser's prompt.
  • Stop and leave stops the run itself: it sets the stop flag, logs auto_match_stopped_on_leave, closes the run record as stopped at once, and saves what was matched so far, before navigating. This doesn't depend on the page unmounting: /map-projects/new and project URLs share one MapProject instance. Unmounting runs the same stop if the dialog didn't. Each run record is closed only once (createRunClosers).
  • Another project gets a new page: MapProject loads its project only on mount, so the routes now render it through MapProjectRoute, which remounts it for a different project URL. The page's own redirect after a save (e.g. /map-projects/new → the saved project) passes mapProjectFrom in the location state and keeps the instance. Only that push or replace keeps it: Back and Forward replay the state, so they always get a new page. When the dialog continues a held Back or Forward as a push, it drops that state. Pages are keyed by a counter, so a new page never reuses an old key.
  • A save doesn't pull the user back: a save's response redirects only if the user is still on the URL it was saved from, and never for a save on leave. This covers saves already in flight when the user left.
  • Nothing is sent after leaving: reranks are neither scheduled nor sent once the page is gone, including a late lookup's rerank that would otherwise count as manual. retryWithBackoff checks for cancellation after its wait too, so an AI call waiting to retry isn't sent again.
  • The leave save is built at once: onSave takes after, a save still in flight. The leave save builds its payload when the user leaves and sends it once that save settles. Late results can't get into it, and it's tracked right away, so reopening the project waits for it (whenSaved).
  • Unsaved projects: on a project that was never saved, the dialog says leaving discards the project and its results, instead of promising a save.
  • No autosave after unmount: scheduleAutoSave does nothing once the page is gone, so late results can't save state frozen at unmount together with live refs.
  • Closing the tab: on pagehide, the run is stopped and its record closed via a new APIService.keepalive() (fetch with keepalive, which outlives the page; axios's XHR doesn't). A close already in flight is resent with its original status.
  • Fix: isRunningBulkScispacyCandidates checked the start time where it should check the end time.
  • Strings added in en, es, pt-BR, zh.

Notes

  • Batches already sent still finish on the server. Their results are dropped.
  • Closing the tab loses results matched since the last save. A full project save is too large to send as the page closes.
  • Handled server-side: if the tab closes while the run's create POST is still in flight, the client never learns the run id, so it can't close the record. OpenConceptLab/ocl_issues#2830 | expire Auto Match runs left running for over 24 hours oclapi2#960 cancels runs left running with no update for 24 hours.

Testing

  • npm test: 596 pass, including new leaveGuard.test.js.
  • Webpack build compiles. Not exercised against a live API. The leave lifecycle in MapProject has no automated test.

🤖 Generated with Claude Code

…ject

- Ask before leaving while a run is going ("Stay" / "Stop and leave"); the
  browser's prompt covers closing the tab.
- Leaving stops the run at once: its record closes as stopped, late results
  are dropped, and no autosave is scheduled after unmount.
- Closing the tab closes the run record with a keepalive request.
- Fix isRunningBulkScispacyCandidates checking the start time for its end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@snyaggarwal snyaggarwal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Found two leave paths that can still leave an Auto Match run active. The new helper tests pass (9/9), but they do not exercise these lifecycle transitions.

Comment thread src/components/map-projects/MapProject.jsx
Comment thread src/components/map-projects/MapProject.jsx
@snyaggarwal

Copy link
Copy Markdown
Contributor Author

Addressed the review (fa48989):

  • [P1] fixed. Stop and leave now stops the run itself before navigating: it sets the stop flag, logs auto_match_stopped_on_leave, closes the run record as stopped, and saves what was matched. It no longer depends on the page unmounting, so it also works when MapProject is reused across project URLs. Unmounting runs the same stop if the dialog didn't, at most once per run. I also found that a save on leave landing while the page was still mounted pushed the project URL and pulled the user back. Saves on leave no longer navigate.
  • [P2] handled server-side in OpenConceptLab/ocl_issues#2830 | expire Auto Match runs left running for over 24 hours oclapi2#960. The client can't identify a run whose create response never arrived, so the API cancels runs left running with no update for 24 hours.

The leave lifecycle in MapProject still has no automated test, and I haven't tried it against a live API.

🤖 Generated with Claude Code

@snyaggarwal snyaggarwal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Re-review of fa48989: the run now stops before navigation, and the companion API PR covers a run whose create response is lost on tab close. Two navigation cases still need attention.

Comment thread src/components/map-projects/MapProject.jsx
Comment thread src/components/map-projects/MapProject.jsx Outdated
…, and keep a save from pulling the user back
@snyaggarwal

Copy link
Copy Markdown
Contributor Author

Addressed the re-review (3feee85):

  • [P1] fixed. Both project routes now render MapProject through a new MapProjectRoute, keyed by the project URL, so going to another project or to /map-projects/new mounts a fresh page that loads that project. The page's own redirect after a save passes the path it left (mapProjectFrom) in the location state and keeps the instance, which /map-projects/new → the saved project relies on. A Back to that history entry gets a new page. The key logic is getPageKey in leaveGuard.js, with tests.
  • [P2] fixed. Each save records the URL it was made from, and its response redirects only if the user is still there. A save already in flight when the user chose Stop and leave no longer pulls them back, whether or not the page was reused.

npm test (590) and the build pass. Not tried against a live API.

🤖 Generated with Claude Code

@snyaggarwal snyaggarwal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Re-review of 3feee85: the previous two navigation findings are addressed. I found one remaining history-navigation bug in the new page-key logic. The 12 helper tests pass, but they omit the direct Back/Forward sequence below.

Comment thread src/components/map-projects/leaveGuard.js Outdated
@snyaggarwal
snyaggarwal requested a review from paynejd October 26, 2026 12:41
@snyaggarwal snyaggarwal self-assigned this Oct 26, 2026
@snyaggarwal

Copy link
Copy Markdown
Contributor Author

Addressed the re-review (1120fa0):

  • [P1] fixed. getPageKey now takes the history action and keeps the page only for the save's own push or replace. Back and Forward (POP) replay the saved entry's mapProjectFrom, so they always get a new page. The key is now a counter instead of the path, so Back to /map-projects/new no longer gets the same key as the page it saved from. Added the new → saved → Back → Forward test.

npm test (591) and the build pass. Not tried against a live API.

🤖 Generated with Claude Code

@snyaggarwal snyaggarwal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Re-reviewed 1120fa0. The direct new-project → saved-project → Back → Forward sequence now remounts on both history moves; the save's immediate push retains the instance as intended. The 13 focused helper tests pass. I found no further actionable issue in this revision. The MapProject leave lifecycle still has no browser/API integration test.

@paynejd

paynejd commented Oct 27, 2026

Copy link
Copy Markdown
Member

Codex review, pass 1 (codex-cli 0.164.0, commit 1120fa0)

An independent pass for my review, run on top of Sunny's four rounds. The scenarios are in the JSON below; Codex says it reproduced each one against the real helpers. I haven't checked them by hand. F1.1 and F1.2 bear on the ticket's second criterion, that nothing spends quota after the user leaves.

Verdict: findings. 5 P1, 1 P2.

  • F1.1 (P1, src/components/map-projects/MapProject.jsx:402): Leaving can still trigger new rerank requests after the run stops.
  • F1.2 (P1, src/components/map-projects/MapProject.jsx:402): Setting abortRef does not cancel an AI retry already sleeping.
  • F1.3 (P1, src/components/map-projects/MapProject.jsx:393): A delayed leave save still combines frozen React state with post-unmount refs.
  • F1.4 (P1, src/components/map-projects/MapProject.jsx:2947): Reopening the project can overtake the newly queued stop-and-leave save.
  • F1.5 (P1, src/components/map-projects/MapProject.jsx:404): Stop and leave discards an unsaved project's results despite promising they are saved.
  • F1.6 (P2, src/components/map-projects/leaveGuard.js:15): Replaying a blocked POP as PUSH defeats the page-remount safeguard.
findings.json
{
  "reviewer": "codex-cli 0.164.0",
  "perspective": "defects",
  "mode": "full",
  "commit": "1120fa0",
  "verdict": "findings",
  "prior": [],
  "findings": [
    {
      "id": "F1.1",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 402,
      "claim": "Leaving can still trigger new rerank requests after the run stops.",
      "scenario": "Stop and leave a multi-algorithm run while candidate lookups are pending. The run's finally clears isBulkMatchRunningRef and bulkMatchAlgoIdsRef. A late lookup then invokes scheduleRerank, which classifies the work as manual and passes isRunTraffic=false. Consequently rerank ignores abortRef and sends new $rerank requests after unmount. This was reproduced using the actual scheduler and rerank functions.",
      "action": "Gate rerank scheduling and sending on the component's lifetime, and prevent late run callbacks from becoming manual traffic after cancellation.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F1.2",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 402,
      "claim": "Setting abortRef does not cancel an AI retry already sleeping.",
      "scenario": "An AI invocation fails to connect and enters retryWithBackoff's delay. The user chooses Stop and leave during that delay. retryWithBackoff checks cancellation before sleeping but not before its next attempt, so another invocation is sent after leaving and can consume an AI call. A direct reproduction confirms the second attempt runs with cancellation already true.",
      "action": "Check cancellation before every retry attempt, including immediately after the backoff delay, and add a regression test for leaving during that delay.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F1.3",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 393,
      "claim": "A delayed leave save still combines frozen React state with post-unmount refs.",
      "scenario": "A project save is uploading when the user stops and leaves an active run. saveOnLeave delays onSave until that upload settles. Meanwhile outstanding match or AI responses still update candidate, stage, and log refs because leaving does not invalidate their run ticket. React state updates after unmount are discarded. The delayed save therefore includes new candidates or completed stages alongside old mappings or missing analysis\u2014the inconsistent snapshot described by the ticket.",
      "action": "Capture a complete immutable save payload before unmount, or prevent all late run responses from mutating the departing project's refs. Test with a pending save and a response arriving after unmount.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F1.4",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 2947,
      "claim": "Reopening the project can overtake the newly queued stop-and-leave save.",
      "scenario": "Save A is pending when Stop and leave queues save B containing the run's results. Reopen the project before A finishes: whenSaved captures only A. Once A settles, B starts, but the reopening GET also starts without waiting for B. It can load A's older contents, and a subsequent autosave can overwrite B's results. This ordering reproduces with the existing save helpers.",
      "action": "Register the entire queued save chain synchronously and make whenSaved wait through its completion. Return the request from onSave so queued saves can be tracked correctly.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F1.5",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 404,
      "claim": "Stop and leave discards an unsaved project's results despite promising they are saved.",
      "scenario": "On /map-projects/new, upload a file, configure matching, and run Auto Match before the first Save; the UI permits this. The leave dialog says matched results are saved, but the scheduler ignores projects without an ID and automatic onSave also returns without one. Choosing Stop and leave therefore discards the project and all results without saving anything.",
      "action": "Persist a valid new project before leaving, or explicitly warn that the unsaved project and results will be discarded instead of promising a save.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F1.6",
      "severity": "P2",
      "tag": null,
      "file": "src/components/map-projects/leaveGuard.js",
      "line": 15,
      "claim": "Replaying a blocked POP as PUSH defeats the page-remount safeguard.",
      "scenario": "Save project A from /map-projects/new, go Back, and start matching another unsaved project. Press Forward to A and choose Stop and leave. continueLeaving pushes A's stored location, retaining mapProjectFrom='/map-projects/new'. getPageKey now sees PUSH and preserves the current key, so A's URL displays the unrelated unsaved project and never loads A.",
      "action": "Remove the save-navigation marker when replaying user navigation, or use a one-time marker that distinguishes an actual save from a replayed history entry. Test continueLeaving and getPageKey together.",
      "lines_delta": null,
      "confidence": "high"
    }
  ]
}

…e leave save at once, and warn when an unsaved project is discarded
@snyaggarwal

Copy link
Copy Markdown
Contributor Author

Addressed the Codex review, pass 1 (3652ac1). All six reproduced against the code.

  • F1.1 fixed. scheduleRerank, its debounce timer, and rerank (including its limiter and capacity waits) stop once the page is unmounted. A late lookup's rerank can no longer go out as manual traffic after leaving.
  • F1.2 fixed. retryWithBackoff checks isCancelled after its wait as well as before. The AI call's cancel check also covers unmount. New src/services/__tests__/retry.test.js covers cancelling during the wait.
  • F1.3 fixed. saveOnLeave now calls save(reasons, {after}) at once. onSave builds the payload then, and sends it once the save in flight settles, so responses that land after leaving can't get into it. A failure building it is still caught and logged, never thrown into the unmount.
  • F1.4 fixed. The leave save is tracked with trackSave when it's queued, as a promise that runs after the earlier save, so whenSaved waits for both. onSave returns its request. A test covers reopening while both are pending.
  • F1.5 fixed. On a project with no id, the dialog says leaving discards the project and its results (leave_while_running_body_unsaved, in all four locales). Saving a new project on leave would need a manual first save with its validation, so I didn't add it.
  • F1.6 fixed. continueLeaving drops mapProjectFrom from the location it replays, so a held Forward to a saved entry gets a new page. A test runs continueLeaving and getPageKey together.

npm test (596) and the build pass. Still not tried against a live API.

🤖 Generated with Claude Code

@snyaggarwal
snyaggarwal merged commit 77d90b7 into main Oct 27, 2026
2 checks passed
@snyaggarwal
snyaggarwal deleted the issues#2830 branch October 27, 2026 02:06
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.

Mapper: an Auto Match run keeps going after you leave the project

2 participants