From b164a5031eee09427a5acb428537c0a47b070486 Mon Sep 17 00:00:00 2001 From: Sertug17 <104278804+Sertug17@users.noreply.github.com> Date: Sat, 29 Aug 2026 22:23:40 +0300 Subject: [PATCH] fix(proxy): pass clientAbort.signal to asset download fetches + add abort guard to chat /imagegen catch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #274. Closes #275. Three asset-download fetch() calls were missing clientAbort.signal — video (line 3215), image gen (line 2771), and img2img (line 2915). Client disconnect during download left the proxy downloading and writing to disk for nobody. Same class as #251. The chat-path /imagegen outer catch was also missing an abort guard, causing bogus error logs on client disconnect. Moved imagegenAbort declaration before the try block so the catch can reference it, matching the pattern in the chat-path /img2img handler. --- src/proxy.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/proxy.ts b/src/proxy.ts index 72f52f5c..c0bd19ff 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -2645,7 +2645,7 @@ export async function startProxy(options: ProxyOptions): Promise { console.log(`[ClawRouter] Image saved → ${img.url}`); } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) { try { - const imgResp = await fetch(img.url); + const imgResp = await fetch(img.url, { signal: clientAbort.signal }); if (imgResp.ok) { const contentType = imgResp.headers.get("content-type") ?? "image/png"; const ext = @@ -2697,6 +2697,10 @@ export async function startProxy(options: ProxyOptions): Promise { // Accepts image as: data URI, local file path, ~/path, or HTTP(S) URL if (req.url === "/v1/images/image2image" && req.method === "POST") { const img2imgStartTime = Date.now(); + const clientAbort = new AbortController(); + res.on("close", () => { + if (!res.writableEnded) clientAbort.abort(); + }); const chunks: Buffer[] = []; for await (const chunk of req) { chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); @@ -2718,7 +2722,7 @@ export async function startProxy(options: ProxyOptions): Promise { // Already a data URI — pass through } else if (val.startsWith("https://") || val.startsWith("http://")) { // Download URL → data URI - const imgResp = await fetch(val); + const imgResp = await fetch(val, { signal: clientAbort.signal }); if (!imgResp.ok) throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`); const contentType = imgResp.headers.get("content-type") ?? "image/png"; @@ -2750,6 +2754,7 @@ export async function startProxy(options: ProxyOptions): Promise { method: "POST", headers: { "content-type": "application/json", "user-agent": USER_AGENT }, body: reqBody, + signal: clientAbort.signal, }); const text = await upstream.text(); if (!upstream.ok) { @@ -2781,7 +2786,7 @@ export async function startProxy(options: ProxyOptions): Promise { console.log(`[ClawRouter] Image saved → ${img.url}`); } else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) { try { - const imgResp = await fetch(img.url); + const imgResp = await fetch(img.url, { signal: clientAbort.signal }); if (imgResp.ok) { const contentType = imgResp.headers.get("content-type") ?? "image/png"; const ext = @@ -3072,7 +3077,7 @@ export async function startProxy(options: ProxyOptions): Promise { for (const clip of finalResult.data) { if (clip.url?.startsWith("https://") || clip.url?.startsWith("http://")) { try { - const videoResp = await fetch(clip.url); + const videoResp = await fetch(clip.url, { signal: clientAbort.signal }); if (videoResp.ok) { const contentType = videoResp.headers.get("content-type") ?? "video/mp4"; const ext = contentType.includes("webm") @@ -3976,6 +3981,10 @@ async function proxyRequest( console.log( `[ClawRouter] /imagegen command → ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`, ); + const imagegenAbort = new AbortController(); + res.on("close", () => { + if (!res.writableEnded) imagegenAbort.abort(); + }); try { const imageUpstreamUrl = `${apiBase}/v1/images/generations`; const imageBody = JSON.stringify({ @@ -3988,6 +3997,7 @@ async function proxyRequest( method: "POST", headers: { "content-type": "application/json", "user-agent": USER_AGENT }, body: imageBody, + signal: imagegenAbort.signal, }); const imageResult = (await imageResponse.json()) as { @@ -4086,6 +4096,7 @@ async function proxyRequest( ); } } catch (err) { + if (imagegenAbort.signal.aborted) return; // client gone — nothing to report const errMsg = err instanceof Error ? err.message : String(err); console.error(`[ClawRouter] /imagegen error: ${errMsg}`); if (!res.headersSent) {