diff --git a/packages/cli/cli/changes/unreleased/fix-remote-gen-polling-hang.yml b/packages/cli/cli/changes/unreleased/fix-remote-gen-polling-hang.yml new file mode 100644 index 000000000000..da52950ca6f9 --- /dev/null +++ b/packages/cli/cli/changes/unreleased/fix-remote-gen-polling-hang.yml @@ -0,0 +1,6 @@ +- summary: | + Fix remote generation CLI hanging indefinitely when S3 download stalls. + Added connection timeout (60s) and overall download timeout (5 min) to + both downloadZipForTask and downloadAndExtractZipToDirectory, so the CLI + fails with a clear error instead of blocking forever. + type: fix diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/RemoteTaskHandler.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/RemoteTaskHandler.ts index 4bf808caa815..8246abc3cf6f 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/RemoteTaskHandler.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/RemoteTaskHandler.ts @@ -480,6 +480,9 @@ async function downloadFilesForTask({ } } +/** Maximum time (ms) to wait for the S3 download to complete, including streaming. */ +const S3_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1_000; + async function downloadZipForTask({ s3PreSignedReadUrl, absolutePathToLocalOutput @@ -489,7 +492,9 @@ async function downloadZipForTask({ }): Promise { // initiate request const request = await axios.get(s3PreSignedReadUrl, { - responseType: "stream" + responseType: "stream", + timeout: 60_000, + signal: AbortSignal.timeout(S3_DOWNLOAD_TIMEOUT_MS) }); // pipe to zip @@ -655,7 +660,9 @@ async function downloadAndExtractZipToDirectory({ outputPath: AbsoluteFilePath; }): Promise { const request = await axios.get(s3PreSignedReadUrl, { - responseType: "stream" + responseType: "stream", + timeout: 60_000, + signal: AbortSignal.timeout(S3_DOWNLOAD_TIMEOUT_MS) }); const tmpDir = await tmp.dir({ prefix: "fern", unsafeCleanup: true });