Skip to content

Commit 6aa129e

Browse files
committed
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
1 parent 8e7a2e7 commit 6aa129e

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

src/core/task/Task.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1663,6 +1663,26 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
16631663
const iterator = stream[Symbol.asyncIterator]()
16641664
let item = await iterator.next()
16651665
while (!item.done) {
1666+
// Check abort flag BEFORE processing the chunk
1667+
if (this.abort) {
1668+
console.log(`aborting stream (early check), this.abandoned = ${this.abandoned}`)
1669+
1670+
if (!this.abandoned) {
1671+
// Only need to gracefully abort if this instance
1672+
// isn't abandoned (sometimes OpenRouter stream
1673+
// hangs, in which case this would affect future
1674+
// instances of Cline).
1675+
await abortStream("user_cancelled")
1676+
}
1677+
1678+
// Clean up the iterator if it has a return method
1679+
if (iterator.return) {
1680+
await iterator.return(undefined).catch(() => {})
1681+
}
1682+
1683+
break // Aborts the stream.
1684+
}
1685+
16661686
const chunk = item.value
16671687
item = await iterator.next()
16681688
if (!chunk) {
@@ -1707,8 +1727,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
17071727
}
17081728
}
17091729

1730+
// Check abort flag AFTER processing the chunk as well
17101731
if (this.abort) {
1711-
console.log(`aborting stream, this.abandoned = ${this.abandoned}`)
1732+
console.log(`aborting stream (after chunk), this.abandoned = ${this.abandoned}`)
17121733

17131734
if (!this.abandoned) {
17141735
// Only need to gracefully abort if this instance
@@ -1718,6 +1739,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
17181739
await abortStream("user_cancelled")
17191740
}
17201741

1742+
// Clean up the iterator if it has a return method
1743+
if (iterator.return) {
1744+
await iterator.return(undefined).catch(() => {})
1745+
}
1746+
17211747
break // Aborts the stream.
17221748
}
17231749

src/core/webview/ClineProvider.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,10 @@ export class ClineProvider
12281228
const rootTask = cline.rootTask
12291229
const parentTask = cline.parentTask
12301230

1231+
// Set the abort flag immediately to signal cancellation
1232+
cline.abort = true
1233+
1234+
// Then call abortTask to handle cleanup
12311235
cline.abortTask()
12321236

12331237
await pWaitFor(
@@ -1243,7 +1247,7 @@ export class ClineProvider
12431247
timeout: 3_000,
12441248
},
12451249
).catch(() => {
1246-
console.error("Failed to abort task")
1250+
console.error("Failed to abort task gracefully")
12471251
})
12481252

12491253
if (this.getCurrentCline()) {

0 commit comments

Comments
 (0)