Skip to content

Commit 84f7196

Browse files
committed
fix: fix cancel button not stopping streaming response
- Move abort check before awaiting iterator.next() to prevent blocking - Add proper stream cleanup with iterator.return() when aborting - Apply same fix to background usage collection loop The issue was that the abort check happened after waiting for the next chunk, which blocked cancellation. Now the check happens before each await, allowing immediate response to cancel requests. Fixes #7014
1 parent 8e7a2e7 commit 84f7196

File tree

1 file changed

+35
-15
lines changed

1 file changed

+35
-15
lines changed

src/core/task/Task.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,11 +1663,32 @@ 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 for abort BEFORE processing the chunk and waiting for the next one
1667+
if (this.abort) {
1668+
console.log(`aborting stream, 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)
1681+
}
1682+
1683+
break // Aborts the stream.
1684+
}
1685+
16661686
const chunk = item.value
1667-
item = await iterator.next()
1687+
16681688
if (!chunk) {
16691689
// Sometimes chunk is undefined, no idea that can cause
16701690
// it, but this workaround seems to fix it.
1691+
item = await iterator.next()
16711692
continue
16721693
}
16731694

@@ -1707,20 +1728,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
17071728
}
17081729
}
17091730

1710-
if (this.abort) {
1711-
console.log(`aborting stream, this.abandoned = ${this.abandoned}`)
1712-
1713-
if (!this.abandoned) {
1714-
// Only need to gracefully abort if this instance
1715-
// isn't abandoned (sometimes OpenRouter stream
1716-
// hangs, in which case this would affect future
1717-
// instances of Cline).
1718-
await abortStream("user_cancelled")
1719-
}
1720-
1721-
break // Aborts the stream.
1722-
}
1723-
17241731
if (this.didRejectTool) {
17251732
// `userContent` has a tool rejection, so interrupt the
17261733
// assistant's response to present the user's feedback.
@@ -1737,6 +1744,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
17371744
"\n\n[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]"
17381745
break
17391746
}
1747+
1748+
// Get next item at the end, after all checks
1749+
item = await iterator.next()
17401750
}
17411751

17421752
// Create a copy of current token values to avoid race conditions
@@ -1815,6 +1825,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
18151825

18161826
// Use the same iterator that the main loop was using
18171827
while (!item.done) {
1828+
// Check for abort first
1829+
if (this.abort) {
1830+
console.log(`[Background Usage Collection] Aborting due to task cancellation`)
1831+
// Clean up the iterator before breaking
1832+
if (iterator.return) {
1833+
await iterator.return(undefined)
1834+
}
1835+
break
1836+
}
1837+
18181838
// Check for timeout
18191839
if (Date.now() - startTime > timeoutMs) {
18201840
console.warn(

0 commit comments

Comments
 (0)