@@ -37,6 +37,14 @@ const PER_ATTEMPT_TIMEOUT_MS = 60_000
3737const POLL_INITIAL_INTERVAL_MS = 500
3838const POLL_MAX_INTERVAL_MS = 5_000
3939const POLL_DEADLINE_MS = 10 * 60_000
40+ /**
41+ * Cap on consecutive failed poll attempts (network errors or retryable HTTP
42+ * statuses). Independent of the 10-minute wall-clock deadline so that
43+ * persistent failures surface in seconds, not minutes — matches the
44+ * MAX_ATTEMPTS shape used by `executeStatement`. Reset to 0 on a successful
45+ * 202 (still-executing) response.
46+ */
47+ const POLL_MAX_CONSECUTIVE_RETRIES = 8
4048/** Maximum number of attempts (including the initial attempt) for retryable POST failures. */
4149const EXECUTE_MAX_ATTEMPTS = 3
4250const EXECUTE_RETRY_BASE_DELAY_MS = 500
@@ -354,6 +362,7 @@ async function pollStatement(input: PollInput): Promise<void> {
354362 } catch ( error ) {
355363 if ( input . signal . aborted ) throw error
356364 retryAttempt ++
365+ if ( retryAttempt > POLL_MAX_CONSECUTIVE_RETRIES ) throw error
357366 const delay = backoffWithJitter ( retryAttempt , null , {
358367 baseMs : EXECUTE_RETRY_BASE_DELAY_MS ,
359368 maxMs : EXECUTE_RETRY_MAX_DELAY_MS ,
@@ -376,6 +385,13 @@ async function pollStatement(input: PollInput): Promise<void> {
376385 }
377386 if ( isRetryableStatus ( response . status ) ) {
378387 retryAttempt ++
388+ if ( retryAttempt > POLL_MAX_CONSECUTIVE_RETRIES ) {
389+ /** Drain the body so undici can return the socket to the keep-alive pool. */
390+ const text = await response . text ( ) . catch ( ( ) => '' )
391+ throw new Error (
392+ `Snowflake poll failed after ${ POLL_MAX_CONSECUTIVE_RETRIES } consecutive retries (HTTP ${ response . status } ): ${ text } `
393+ )
394+ }
379395 const retryAfterMs = parseRetryAfter ( response . headers . get ( 'Retry-After' ) )
380396 const delay = backoffWithJitter ( retryAttempt , retryAfterMs , {
381397 baseMs : EXECUTE_RETRY_BASE_DELAY_MS ,
0 commit comments