Skip to content

fix(spa): don't attribute a page-hide to a turn that already finished - #1370

Merged
philmerrell merged 1 commit into
developfrom
fix/pagehide-skip-finished-turns
Sep 27, 2026
Merged

philmerrell merged 1 commit into
developfrom
fix/pagehide-skip-finished-turns

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Follow-up to #1366, closing the gap its "Not in this PR" section named.

Problem

The pagehide handler in chat-http.service.ts attributes a page departure to every session in streamingSessionIds() and POSTs navigated_away for each. A session stays in that list until the transport's onclose, which can trail the server's done frame (widest on a first turn, where session_title can arrive after done). A refresh or tab close in that window stamps navigated_away on a turn that already finished.

The backend gate in POST /sessions/{id}/interrupt records navigated_away only while the single-flight lease is held. That doesn't fully close the gap: the lease is released in the stream generator's finally, which can itself run after done has been sent.

Fix

Skip any session where StreamParserService.hasReceivedDone(sessionId) is true, the same check #1366 added to cancelChatRequest. Mid-stream departures are still attributed exactly as before.

Tests

Two specs added to the existing race block in chat-http.service.spec.ts (renamed "Stop or page-hide racing the end of the turn"). They use the same open-stream harness as the Stop specs:

  • Page-hide after done, before close: sends no interrupt POST and doesn't abort the stream.
  • Page-hide mid-stream: still sends one keepalive navigated_away POST for the session.

With the fix reverted locally, the first spec fails and the second passes, as expected.

npx ng test --watch=false: 324 files, 4082 tests passed.

🤖 Generated with Claude Code

Page-hide attribution picks turns from streamingSessionIds(), which still
lists a session until the transport closes, and close can trail `done`. A
departure in that window POSTed `navigated_away` for a finished turn. The
backend lease gate doesn't fully cover it, because the lease is released in
the stream generator's `finally`, which can itself come after `done`.

Skip sessions where hasReceivedDone() is true, as the Stop path does since
#1366.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@philmerrell
philmerrell merged commit e0923ba into develop Sep 27, 2026
7 checks passed
@philmerrell
philmerrell deleted the fix/pagehide-skip-finished-turns branch September 27, 2026 17:43
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.

1 participant