useAsync.run hands an overtaken answer back to nobody (#941) #947

Merged
claude-bot merged 1 commit from fix/useasync-overtaken into main 2026-09-30 04:58:03 +00:00
Collaborator

Closes #941, the follow-up to #932.

The bug: useAsync kept an overtaken answer out of its own state, but run() still resolved with it. So a caller that writes the result into its own state could draw a stale poll over a newer answer, as #932 did on the session page. The callers affected:

  • useRead (the Review tab);
  • PickedFold;
  • the Try again buttons in NotesPanel, ConfirmedView, PencilledView and VoteView.

The fix: run() resolves to undefined once a newer run, or reset(), has overtaken it. Callers already read undefined as "nothing to draw".

Audit: I checked the other result-consuming callers in #941. None needs an overtaken run's answer: the concurrent ones ignore the result, and saves act on the newest run's answer.

Tests: two in useAsync.test.jsx. Both fail on main's hook and pass here. The full vitest suite passes (2758) and eslint shows 0 errors.

Changelog: changelog.d/941-useasync-overtaken.fixed.md.

🤖 Generated with Claude Code

Closes #941, the follow-up to #932. **The bug:** `useAsync` kept an overtaken answer out of its own state, but `run()` still resolved with it. So a caller that writes the result into its own state could draw a stale poll over a newer answer, as #932 did on the session page. The callers affected: - `useRead` (the Review tab); - `PickedFold`; - the Try again buttons in NotesPanel, ConfirmedView, PencilledView and VoteView. **The fix:** `run()` resolves to `undefined` once a newer run, or `reset()`, has overtaken it. Callers already read `undefined` as "nothing to draw". **Audit:** I checked the other result-consuming callers in #941. None needs an overtaken run's answer: the concurrent ones ignore the result, and saves act on the newest run's answer. **Tests:** two in `useAsync.test.jsx`. Both fail on main's hook and pass here. The full vitest suite passes (2758) and eslint shows 0 errors. Changelog: `changelog.d/941-useasync-overtaken.fixed.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(ui): useAsync.run hands an overtaken answer back to nobody (#941)
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 39s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m9s
CI / Feedback site tests (pull_request) Successful in 49s
CI / Bot/backend version sync (pull_request) Successful in 38s
CI / Does this change touch the recording path? (pull_request) Successful in 47s
CI / Bot tests and audit (pull_request) Successful in 2m1s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Docker image build (pull_request) Successful in 2m13s
CI / Frontend (shard 2/2) (pull_request) Successful in 7m4s
CI / Frontend (shard 1/2) (pull_request) Successful in 9m16s
CI / End-to-end journeys (phone) (pull_request) Successful in 7m56s
CI / End-to-end journeys (desktop) (pull_request) Successful in 8m54s
CI / Backend migration, tests, and audit (pull_request) Successful in 17m24s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 16m6s
96a6260cce
useAsync kept an overtaken answer out of its own state but still resolved
run() with it, so any caller writing the result into its own state (the
Review tab's useRead, PickedFold, the Try again buttons) could draw a
stale poll over a newer answer, as #932 did on the session page. run()
now resolves undefined once a newer run or reset() has overtaken it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.