Skip to content

Commit a142f0c

Browse files
committed
fix: retry API requests on stream failures instead of aborting task
Previously, when a stream failed mid-execution, the task would abort entirely, requiring manual intervention to resume. This change modifies the error handling logic to distinguish between user cancellations and stream failures. Changes: - Stream failures now clean up partial state and retry automatically - User cancellations still abort the task as expected - Retry uses existing rate limiting to prevent rapid request loops - Failed requests are retried from the start with the same content This allows tasks to recover gracefully from transient network issues or temporary API failures without losing progress.
1 parent b9110dc commit a142f0c

File tree

1 file changed

+20
-12
lines changed

1 file changed

+20
-12
lines changed

src/core/task/Task.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,28 +2186,36 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
21862186
// Cline instance to finish aborting (error is thrown here when
21872187
// any function in the for loop throws due to this.abort).
21882188
if (!this.abandoned) {
2189-
// If the stream failed, there's various states the task
2190-
// could be in (i.e. could have streamed some tools the user
2191-
// may have executed), so we just resort to replicating a
2192-
// cancel task.
2193-
2194-
// Determine cancellation reason BEFORE aborting to ensure correct persistence
2189+
// Determine cancellation reason
21952190
const cancelReason: ClineApiReqCancelReason = this.abort ? "user_cancelled" : "streaming_failed"
21962191

21972192
const streamingFailedMessage = this.abort
21982193
? undefined
21992194
: (error.message ?? JSON.stringify(serializeError(error), null, 2))
22002195

2201-
// Persist interruption details first to both UI and API histories
2196+
// Clean up partial state
22022197
await abortStream(cancelReason, streamingFailedMessage)
22032198

2204-
// Record reason for provider to decide rehydration path
2205-
this.abortReason = cancelReason
2199+
if (this.abort) {
2200+
// User cancelled - abort the entire task
2201+
this.abortReason = cancelReason
2202+
await this.abortTask()
2203+
} else {
2204+
// Stream failed - log the error and retry with the same content
2205+
// The existing rate limiting will prevent rapid retries
2206+
console.error(
2207+
`[Task#${this.taskId}.${this.instanceId}] Stream failed, will retry: ${streamingFailedMessage}`,
2208+
)
22062209

2207-
// Now abort (emits TaskAborted which provider listens to)
2208-
await this.abortTask()
2210+
// Push the same content back onto the stack to retry
2211+
stack.push({
2212+
userContent: currentUserContent,
2213+
includeFileDetails: false,
2214+
})
22092215

2210-
// Do not rehydrate here; provider owns rehydration to avoid duplication races
2216+
// Continue to retry the request
2217+
continue
2218+
}
22112219
}
22122220
} finally {
22132221
this.isStreaming = false

0 commit comments

Comments
 (0)