Skip to content

Conversation

@daniel-lxs
Copy link
Member

@daniel-lxs daniel-lxs commented Aug 7, 2025

Fixes #6726

Problem

When cancelling a task, the entire UI would rerender because initClineWithHistoryItem was being called, which recreated the Task instance from scratch. This caused a noticeable flicker/blink in the UI.

Solution

This PR refactors the cancellation flow to preserve the Task instance throughout the cancellation and resumption process:

Key Changes:

  1. Added resetToResumableState() method to the Task class that resets internal state without recreating the instance
  2. Simplified cancellation flow - Task now handles its own cancellation and resumption internally
  3. Updated abortStream() to properly mark API requests as cancelled in the UI
  4. Removed initClineWithHistoryItem() call from ClineProvider.cancelTask() - now just sets the abort flag

How it works:

  • When user cancels: ClineProvider sets task.abort = true
  • The streaming loop detects abort and calls abortStream() to update API status
  • After stream exits, checks if aborted and calls resetToResumableState()
  • Shows resume prompt and continues with user's new input
  • No Task recreation = No UI rerender

Result

✅ No UI flicker when cancelling tasks
✅ API request properly shows as cancelled (not stuck loading)
✅ Immediate resumption - users can send messages right after cancellation
✅ Cleaner architecture with clear separation of concerns

Testing

  • Cancel a task mid-stream and verify no UI flicker
  • Verify API request shows as cancelled
  • Verify you can immediately send a new message after cancellation
  • Verify task resumes properly with new instructions

Important

Refactor task cancellation flow to prevent UI rerender by introducing resetToResumableState() in Task.ts and updating ClineProvider.ts.

  • Behavior:
    • Refactor task cancellation flow to prevent UI rerender in Task.ts and ClineProvider.ts.
    • Introduce resetToResumableState() in Task to reset state without recreating the instance.
    • Simplify task cancellation by handling it internally within Task.
  • Functions:
    • Update abortTask() in Task.ts to include skipSave parameter.
    • Remove initClineWithHistoryItem() call from ClineProvider.cancelTask().
    • Add handleUserCancellationResume() in Task.ts for resumption logic.
  • Misc:
    • Add timeout in ClineProvider.cancelTask() to force abort if task doesn't respond.

This description was created by Ellipsis for 8f9fcc3. You can customize this summary. It will automatically update as commits are pushed.

@daniel-lxs daniel-lxs requested review from cte, jr and mrubens as code owners August 7, 2025 00:06
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working labels Aug 7, 2025
Copy link
Contributor

@roomote roomote bot left a comment

Choose a reason for hiding this comment

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

Thank you for your contribution! I've reviewed the changes and this is a good architectural improvement that successfully eliminates UI flicker by preserving the Task instance. I have some suggestions for improvement, particularly around resource cleanup and error handling.

* Reset the task to a resumable state without recreating the instance.
* This is used when canceling a task to avoid unnecessary rerenders.
*/
public async resetToResumableState() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Potential memory leak concern: This method resets many properties but doesn't dispose of resources like urlContentFetcher, browserSession, or terminalProcess. These could accumulate if tasks are repeatedly cancelled and resumed.

Consider adding cleanup for these resources:

}

public async abortTask(isAbandoned = false) {
public async abortTask(isAbandoned = false, skipSave = false) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding JSDoc documentation for the new skipSave parameter to clarify when it should be used:

this.getCurrentCline()!.abandoned = true
}
// Just set the abort flag - the task will handle its own resumption
cline.abort = true
Copy link
Contributor

Choose a reason for hiding this comment

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

The simplified cancellation removes the timeout and abandonment logic. While cleaner, this could potentially leave tasks hanging if the streaming loop doesn't detect the abort flag quickly. Is this intentional? You might want to keep a timeout as a safety net:

@hannesrudolph hannesrudolph added the Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. label Aug 7, 2025
@daniel-lxs daniel-lxs moved this from Triage to PR [Needs Review] in Roo Code Roadmap Aug 7, 2025
@hannesrudolph hannesrudolph added PR - Needs Review and removed Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. labels Aug 7, 2025
@daniel-lxs
Copy link
Member Author

@roomote-agent use the switch_mode tool to switch to pr-fixer mode and solve the conflicts on this PR, do not create a new one

@roomote
Copy link
Contributor

roomote bot commented Aug 28, 2025

Hi @daniel-lxs! I see your request to resolve the merge conflicts on this PR. I'll work on resolving them now and push the changes to the same branch. Will update you once completed.

- Add resetToResumableState() method to Task class to reset internal state without recreation
- Simplify cancellation flow to handle everything within Task class
- Update abortStream() to properly mark API requests as cancelled
- Remove initClineWithHistoryItem() call from ClineProvider.cancelTask()
- Task instance now persists through cancellation, preventing UI flicker

The task now handles its own cancellation and resumption internally, maintaining
the same instance throughout. This provides a seamless user experience with no
visual disruption when cancelling and resuming tasks.
- Add proper resource disposal in resetToResumableState() to prevent memory leaks
- Add JSDoc documentation for skipSave parameter in abortTask()
- Add error handling for resetToResumableState() and ask() operations
- Extract resumption logic into dedicated handleUserCancellationResume() method
- Add 30-second timeout safety net for cancellation in ClineProvider

These changes ensure proper cleanup of resources (browsers, terminals) during
task cancellation while maintaining the no-rerender behavior.
@roomote roomote bot force-pushed the fix/task-cancellation-no-rerender branch from 9f263ed to adcc10a Compare August 28, 2025 17:33
// Add a timeout safety net to ensure the task doesn't hang indefinitely
// If the task doesn't respond to cancellation within 30 seconds, force abort it
const timeoutMs = 30000 // 30 seconds
const timeoutPromise = new Promise<void>((resolve) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

The timeout safety net in cancelTask is defined but not awaited. Consider using Promise.race or otherwise awaiting the timeout promise to ensure the forced abort occurs reliably if the task doesn’t respond to cancellation.

@roomote
Copy link
Contributor

roomote bot commented Aug 28, 2025

Merge conflicts resolved successfully!

I've successfully resolved the merge conflicts on this PR by rebasing onto the latest main branch. Here's what was done:

Changes Made:

  1. Rebased the branch onto the latest origin/main to incorporate recent changes
  2. Resolved conflicts in:
    • src/core/task/Task.ts - Kept the PR's changes including:
      • Added JSDoc documentation for the abortTask method
      • Added the skipSave parameter to prevent redundant saves during user cancellation
      • Added the handleUserCancellationResume() method for handling resumption after cancellation
      • Preserved the resetToResumableState() method that resets task state without recreation
    • src/core/webview/ClineProvider.ts - Kept the simplified cancellation flow

Key Architectural Changes Preserved:

  • ✅ Task instance is preserved during cancellation (no recreation)
  • ✅ Simplified cancellation flow with the Task handling its own state
  • ✅ Added skipSave parameter to avoid redundant saves
  • ✅ Proper cleanup of resources in resetToResumableState()
  • ✅ UI flickering issue should be resolved

Test Results:

  • ✅ All Task.spec.ts tests passing (29 passed, 4 skipped)
  • ✅ Most CI checks have passed
  • ⏳ Integration tests and Windows unit tests still running

The PR is ready for review once all CI checks complete. The merge conflicts have been cleanly resolved while preserving all the intended functionality of this fix.

* IMPORTANT: This method cleans up resources to prevent memory leaks
* while preserving the task instance for resumption.
*/
public async resetToResumableState() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel nervous about this logic getting out of sync with logic in other parts of the codebase (like the "// Reset streaming state" logic a couple hundred lines down). Think there's any way to DRY it up so we don't forget to add things?

* high-level control flags like isStreaming/isWaitingForFirstChunk.
* Those are managed by their respective flows.
*/
private async resetStreamingState(options: { resetDiffView?: "always" | "ifEditing" | "never" } = {}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Inconsistency in parameter naming: The comment describes the resetDiffView option as accepting 'always, only-if-editing, or never', but the code defines it as 'always', 'ifEditing', or 'never'. Consider updating the comment or the parameter type for consistency.

@daniel-lxs daniel-lxs marked this pull request as draft August 30, 2025 21:25
@daniel-lxs daniel-lxs moved this from PR [Needs Review] to PR [Draft / In Progress] in Roo Code Roadmap Aug 30, 2025
@github-project-automation github-project-automation bot moved this from PR [Draft / In Progress] to Done in Roo Code Roadmap Sep 23, 2025
@github-project-automation github-project-automation bot moved this from New to Done in Roo Code Roadmap Sep 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working PR - Draft / In Progress size:L This PR changes 100-499 lines, ignoring generated files.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Chat re-renders on OpenAI compat when clicking Cancel, not gracefully exitting (disregard the model's response)

4 participants