Skip to content

Stale chat progress can finish a newer active turn #417

Description

@jdneo

Context

Discovered while reviewing #378, which fixes the confirmed #99 path where rebuilding the action buttons renders a disabled Send button during an active turn.

This issue tracks a separate turn-lifecycle race that can produce a similar symptom. It is not currently established as a root cause of #99.

Related review thread: #378 (comment)

Problem

Each chat request is sent with a unique workDoneToken, and ProgressParamsAdapter parses the token returned on $/progress. However, CopilotLanguageClient.notifyProgress() forwards only ChatProgressValue, so the request identity is unavailable to ChatView.

ChatView currently associates top-level progress primarily by conversationId. Consecutive turns in one conversation share that ID, and any accepted top-level end event calls actionBar.markTurnFinished(). Asynchronous request errors also finish the action bar without checking whether the failed request is still active.

A possible sequence is:

  1. Turn A is running.
  2. The user cancels A; the UI immediately allows another send.
  3. Turn B starts in the same conversation.
  4. A delayed end(A) or error callback from A arrives.
  5. B is incorrectly marked finished and its Cancel button becomes Send.
  6. Subsequent progress for B may be ignored because onChatProgress() gates processing on actionBar.isTurnRunning().

A real log attached to #99 confirms that the language server can send a cancellation end after $/cancelRequest (about 15 ms later in that trace). That trace did not overlap the cancellation end with the next turn, so it demonstrates the event shape but not this race itself.

Expected behavior

A terminal progress event or asynchronous callback must only finish the top-level request to which it belongs. Events from an older request must not change the lifecycle or action-button state of a newer active turn.

Acceptance criteria

  • start A -> cancel A -> start B -> end A leaves B running with Cancel available.
  • A delayed error callback from A cannot finish B.
  • The matching end B still finishes B normally.
  • Request identity is preserved sufficiently to distinguish consecutive turns in the same conversation.
  • The lifecycle behavior is covered by automated tests.
  • Detailed handling of stale events, persistence, conversation metadata, and subagent progress is decided during implementation design rather than assumed by this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions