From 6a4e99aee3834ed2541578e9b1037c1ece3854df Mon Sep 17 00:00:00 2001 From: jupiterv2 Date: Tue, 16 Jun 2026 15:38:25 +0800 Subject: [PATCH] fix(cli): retry processor upload on ECONNRESET and other transient errors The upload step PUTs the packed processor to a signed storage URL via node-fetch. node-fetch intermittently surfaces a "socket hang up" (ECONNRESET) against GCS, but the retry guard only matched EPIPE, so a single reset aborted the whole upload with no retry. Widen the retry whitelist to cover the common transient socket errnos (ECONNRESET, ETIMEDOUT, ECONNREFUSED, EAI_AGAIN) in addition to EPIPE. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/src/commands/upload.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/upload.ts b/packages/cli/src/commands/upload.ts index 49f66154..570ab0db 100644 --- a/packages/cli/src/commands/upload.ts +++ b/packages/cli/src/commands/upload.ts @@ -738,7 +738,12 @@ export async function uploadFile( try { await upload() } catch (e: any) { - if (e?.constructor.name === 'FetchError' && e.type === 'system' && e.code === 'EPIPE') { + // Transient network errors while PUT-ing the packed processor to the signed + // storage URL: node-fetch surfaces these as `FetchError` with `type: 'system'` + // and the underlying socket errno in `code`. Retry all of them, not just EPIPE + // (ECONNRESET / "socket hang up" is the most common one against GCS). + const RETRYABLE_CODES = ['EPIPE', 'ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'EAI_AGAIN'] + if (e?.constructor.name === 'FetchError' && e.type === 'system' && RETRYABLE_CODES.includes(e.code)) { error = e await new Promise((resolve) => setTimeout(resolve, 1000)) await tryUploading()