Skip to content

Commit e180404

Browse files
committed
fix(providers): limit transport retries to network socket codes
1 parent b6b8b9d commit e180404

3 files changed

Lines changed: 20 additions & 2 deletions

File tree

‎apps/sim/lib/core/errors/retryable-infrastructure.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ export function isRetryableInfrastructureError(error: unknown): boolean {
9797
return Boolean(describeRetryableInfrastructureError(error))
9898
}
9999

100+
/**
101+
* A network-level failure only — a dropped, refused, or timed-out socket anywhere in the
102+
* cause chain. Narrower than {@link isRetryableInfrastructureError}: database and application
103+
* codes are excluded, so a caller replaying a billed request never mistakes one for a socket.
104+
*/
105+
export function isRetryableNetworkError(error: unknown): boolean {
106+
return getErrorChain(error).some(
107+
(candidate) =>
108+
(typeof candidate.code === 'string' && RETRYABLE_NETWORK_ERROR_CODES.has(candidate.code)) ||
109+
(typeof candidate.errno === 'string' && RETRYABLE_NETWORK_ERROR_CODES.has(candidate.errno))
110+
)
111+
}
112+
100113
/**
101114
* A retryable infrastructure failure raised strictly BEFORE the guarded
102115
* operation performed any effect (no workflow block ran, no mutation

‎apps/sim/providers/retry.test.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ describe('fetchWithProviderRetry', () => {
227227
it.each([
228228
['an unrecognised failure', new Error('body serialization failed')],
229229
['a TypeError from building the request', new TypeError('Invalid URL')],
230+
[
231+
'an application error code',
232+
Object.assign(new Error('overloaded'), { code: 'RESOURCE_EXHAUSTED' }),
233+
],
234+
['a database error code', Object.assign(new Error('admin shutdown'), { code: '57P01' })],
230235
])('does not retry %s', async (_, failure) => {
231236
const send = vi.fn().mockRejectedValue(failure)
232237

‎apps/sim/providers/retry.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { isRecordLike } from '@sim/utils/object'
2222
import { backoffWithJitter, parseRetryAfter } from '@sim/utils/retry'
2323
import { truncate } from '@sim/utils/string'
2424
import { isQuotaExhaustionBody } from '@/lib/core/errors/provider-quota'
25-
import { isRetryableInfrastructureError } from '@/lib/core/errors/retryable-infrastructure'
25+
import { isRetryableNetworkError } from '@/lib/core/errors/retryable-infrastructure'
2626
import {
2727
DEFAULT_MAX_ERROR_BODY_BYTES,
2828
readResponseTextWithLimit,
@@ -80,7 +80,7 @@ export function isWithinRetryWindow(retryAfterMs: number | null): boolean {
8080
function isRetryableTransportFailure(error: unknown): boolean {
8181
const code = isRecordLike(error) ? error.code : undefined
8282
if (typeof code === 'string' && BUN_CONNECTION_ERROR_CODES.has(code)) return true
83-
return isRetryableInfrastructureError(error)
83+
return isRetryableNetworkError(error)
8484
}
8585

8686
/**

0 commit comments

Comments
 (0)