From 27badb2584a9131b9408dacc1f58417745968dbe Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Fri, 13 Feb 2026 04:21:13 +0700 Subject: [PATCH] fix(cursor): validate fetch status before streaming fallback and register abort early - Check response.status before calling transformProtobufToSSE in fetch fallback - Move abort signal registration before response headers to handle early cancellation --- src/cursor/cursor-executor.ts | 39 ++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/cursor/cursor-executor.ts b/src/cursor/cursor-executor.ts index 6153c24c..c404d1a9 100644 --- a/src/cursor/cursor-executor.ts +++ b/src/cursor/cursor-executor.ts @@ -378,6 +378,19 @@ export class CursorExecutor { const http2 = await getHttp2(); if (!http2) { const response = await this.makeFetchRequest(url, headers, body, signal); + if (response.status !== 200) { + const errorText = response.body?.toString() || 'Unknown error'; + return new Response( + JSON.stringify({ + error: { + message: `[${response.status}]: ${errorText}`, + type: response.status === 429 ? 'rate_limit_error' : 'invalid_request_error', + code: '', + }, + }), + { status: response.status, headers: { 'Content-Type': 'application/json' } } + ); + } return this.transformProtobufToSSE(response.body, model, requestBody); } @@ -401,6 +414,20 @@ export class CursorExecutor { ...headers, }); + // Register abort before response headers arrive so cancellation works at all stages + let streamClosed = false; + if (signal) { + const onAbort = () => { + streamClosed = true; + req.close(); + client.close(); + }; + signal.addEventListener('abort', onAbort, { once: true }); + const cleanup = () => signal.removeEventListener('abort', onAbort); + req.on('end', cleanup); + req.on('error', cleanup); + } + req.on('response', (hdrs) => { const status = Number(hdrs[':status']) || 500; @@ -441,7 +468,6 @@ export class CursorExecutor { >(); let chunkCount = 0; let toolCallCount = 0; - let streamClosed = false; const readable = new ReadableStream({ start(controller) { @@ -590,17 +616,6 @@ export class CursorExecutor { } client.close(); }); - - if (signal) { - const onAbort = () => { - req.close(); - closeStream(); - }; - signal.addEventListener('abort', onAbort, { once: true }); - const cleanup = () => signal.removeEventListener('abort', onAbort); - req.on('end', cleanup); - req.on('error', cleanup); - } }, });