Skip to content

fix: SSE reconnect followup - reset refs, prevent duplication, sort active requests#7992

Closed
jh-block wants to merge 4 commits into
mainfrom
jhugo/fix-sse-reconnect-followup
Closed

fix: SSE reconnect followup - reset refs, prevent duplication, sort active requests#7992
jh-block wants to merge 4 commits into
mainfrom
jhugo/fix-sse-reconnect-followup

Conversation

@jh-block
Copy link
Copy Markdown
Collaborator

@jh-block jh-block commented Mar 18, 2026

Summary

Follow-up fixes to #7834 (SSE reconnect):

  • Reset request-tracking refs on session switch — clear all active/pending/buffer refs in the session-load effect cleanup so switching sessions doesn't leak state from the previous session's in-flight request
  • Prevent overflow duplication — clear active request state in reloadConversation so the SSE reconnect doesn't reattach and replay deltas on top of the freshly-loaded conversation after a buffer overflow
  • Sort ActiveRequests by ID — use lexicographic sort on uuidv7 IDs to always pick the newest reply, not an arbitrary HashMap key order

… active requests

- Clear all request-tracking refs (active, pending, buffer) in the
  session-load effect cleanup so switching sessions doesn't leak
  state from the previous session.
- Clear active request state in reloadConversation so the SSE
  reconnect doesn't reattach and replay deltas on top of the
  freshly-loaded conversation after a buffer overflow.
- Sort ActiveRequests IDs lexicographically (uuidv7 is time-ordered)
  to always pick the newest reply, not an arbitrary HashMap key.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b336664e8d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/desktop/src/hooks/useChatStream.ts
Comment thread ui/desktop/src/hooks/useChatStream.ts Outdated
…ch during reload

- Extract request-tracking ref cleanup into a separate useEffect so it
  runs on ALL session changes, including cache hits (previously the
  cleanup was inside the resumeAgent branch and skipped for cached sessions)
- Add reloadingConversationRef flag to suppress ActiveRequests reattach
  while getSession is in flight during buffer overflow recovery, preventing
  replayed deltas from being appended to stale messages
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4471aacaad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/desktop/src/hooks/useChatStream.ts Outdated
ActiveRequests is only sent once per SSE connection (before replay).
If it arrives while reloadConversation's getSession is in flight, we
now buffer events using the same cold-mount pattern instead of
discarding the request IDs. Once the reload completes, the deferred
reattach runs with the fresh conversation snapshot.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56ed38bb2f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +486 to +488
const pendingRequestId = pendingReattachRequestIdRef.current;
if (pendingRequestId) {
doReattachRef.current?.(pendingRequestId, messages);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Scope deferred overflow reattach to the session being reloaded

If the user switches from session A to session B while reloadConversation() for A is still waiting on getSession(), these lines read pendingReattachRequestIdRef and doReattachRef after those shared refs have already been repointed at B by the new render. Unlike the resumeAgent load effect later in this file, reloadConversation has no cancellation/session guard, so A's stale reload can invoke doReattach for B with A's message snapshot and corrupt the newly opened chat.

Useful? React with 👍 / 👎.

Comment on lines 490 to +492
}).catch((e) => {
console.warn('Failed to reload conversation after buffer overflow:', e);
reloadingConversationRef.current = false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recover buffered events when the overflow reload fetch fails

By the time execution reaches this catch, reloadConversation() has already unsubscribed the active processor and cleared the request refs. If /sessions/{id} transiently fails during buffer-overflow recovery, the replacement listener created by ActiveRequests is still only appending to pendingReattachBufferRef, but this path never calls doReattach or restores the old listener. Because ActiveRequests is only sent once per SSE connection, the in-flight reply can stay permanently stuck until the user remounts the session.

Useful? React with 👍 / 👎.

- Capture sessionId at reload start and guard the .then()/.catch()
  callbacks so a stale reload from session A can't corrupt session B
  after a session switch
- On getSession failure, attempt best-effort reattach with current
  messages so a transient error doesn't permanently lose the in-flight
  reply
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 351e17150b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +489 to +492
if (messages.length > 0) {
dispatch({ type: 'SET_MESSAGES', payload: messages });
}
reloadingConversationRef.current = false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep overflow reload suppressed until the new messages render

Fresh evidence in this follow-up patch: reloadingConversationRef is cleared here before React has committed the SET_MESSAGES dispatch, and stateRef.current is only refreshed on the next render (ui/desktop/src/hooks/useChatStream.ts:377-380). If the reconnected SSE delivers ActiveRequests in that gap, the handler at ui/desktop/src/hooks/useChatStream.ts:593-615 stops buffering and calls doReattach(...) with the stale pre-overflow messages, so replayed deltas get appended to the wrong snapshot again.

Useful? React with 👍 / 👎.

Comment on lines +484 to +485
// Session switched while we were reloading — discard stale result
if (reloadSessionId !== sessionIdRef.current) return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject stale overflow reloads from an earlier visit to the same session

This guard only compares the stored session ID to sessionIdRef.current, so a getSession() started during an earlier visit to session A is still accepted if the user navigates A → B → A before it resolves. Unlike the resumeAgent effect below, there is no per-load cancellation token, so that late callback can overwrite the newer A state with an outdated snapshot and optionally run a deferred reattach against the wrong generation of the chat.

Useful? React with 👍 / 👎.

@DOsinga
Copy link
Copy Markdown
Collaborator

DOsinga commented Mar 20, 2026

Closing in favour of a fresh PR that addresses the remaining Codex P1 issues (React commit timing race and A→B→A generation counter). Thanks for the work on this — the session-switch cleanup and overflow guard approach are the right direction.

@DOsinga DOsinga closed this Mar 20, 2026
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.

2 participants