fix: ensure cancel button properly aborts streaming responses

- Set abort flag immediately in ClineProvider.cancelTask() before calling abortTask()
- Add early abort check at the beginning of streaming loop before processing chunks
- Add proper iterator cleanup when aborting stream
- Keep existing abort check after chunk processing for redundancy

This ensures the cancel button works even when chunks are arriving constantly,
addressing the issue where the abort flag appeared to never be set.

Fixes #7014
This commit is contained in:
Roo Code 2025-08-12 19:18:22 +00:00
parent 8e7a2e7bdb
commit 6aa129ec5e
2 changed files with 32 additions and 2 deletions

View file

@ -1663,6 +1663,26 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
const iterator = stream[Symbol.asyncIterator]()
let item = await iterator.next()
while (!item.done) {
// Check abort flag BEFORE processing the chunk
if (this.abort) {
console.log(`aborting stream (early check), this.abandoned = ${this.abandoned}`)
if (!this.abandoned) {
// Only need to gracefully abort if this instance
// isn't abandoned (sometimes OpenRouter stream
// hangs, in which case this would affect future
// instances of Cline).
await abortStream("user_cancelled")
}
// Clean up the iterator if it has a return method
if (iterator.return) {
await iterator.return(undefined).catch(() => {})
}
break // Aborts the stream.
}
const chunk = item.value
item = await iterator.next()
if (!chunk) {
@ -1707,8 +1727,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
}
// Check abort flag AFTER processing the chunk as well
if (this.abort) {
console.log(`aborting stream, this.abandoned = ${this.abandoned}`)
console.log(`aborting stream (after chunk), this.abandoned = ${this.abandoned}`)
if (!this.abandoned) {
// Only need to gracefully abort if this instance
@ -1718,6 +1739,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
await abortStream("user_cancelled")
}
// Clean up the iterator if it has a return method
if (iterator.return) {
await iterator.return(undefined).catch(() => {})
}
break // Aborts the stream.
}

View file

@ -1228,6 +1228,10 @@ export class ClineProvider
const rootTask = cline.rootTask
const parentTask = cline.parentTask
// Set the abort flag immediately to signal cancellation
cline.abort = true
// Then call abortTask to handle cleanup
cline.abortTask()
await pWaitFor(
@ -1243,7 +1247,7 @@ export class ClineProvider
timeout: 3_000,
},
).catch(() => {
console.error("Failed to abort task")
console.error("Failed to abort task gracefully")
})
if (this.getCurrentCline()) {