From 53072627bad17fe6ed23f82bfd51095a82cd4f9b Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Mon, 15 Jun 2026 13:03:33 +0200 Subject: [PATCH 1/3] fix: Consider all HTTP errors as service failures except 429 --- .../src/create-service-policy.ts | 7 +++++- .../src/rpc-service/rpc-service.ts | 23 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/controller-utils/src/create-service-policy.ts b/packages/controller-utils/src/create-service-policy.ts index 0c27fdf615..dcac214999 100644 --- a/packages/controller-utils/src/create-service-policy.ts +++ b/packages/controller-utils/src/create-service-policy.ts @@ -53,6 +53,10 @@ export type CreateServicePolicyOptions = { * regarded as degraded (affecting when `onDegraded` is called). */ degradedThreshold?: number; + /** + * Predicate function for when an error should be considered a service failure. + */ + isServiceFailure?: (error: unknown) => boolean; /** * The maximum number of times that the service is allowed to fail before * pausing further retries. @@ -189,7 +193,7 @@ export const DEFAULT_CIRCUIT_BREAK_DURATION = 30 * 60 * 1000; */ export const DEFAULT_DEGRADED_THRESHOLD = 5_000; -const isServiceFailure = (error: unknown): boolean => { +const defaultIsServiceFailure = (error: unknown): boolean => { if ( typeof error === 'object' && error !== null && @@ -283,6 +287,7 @@ export function createServicePolicy( circuitBreakDuration = DEFAULT_CIRCUIT_BREAK_DURATION, degradedThreshold = DEFAULT_DEGRADED_THRESHOLD, backoff = new ExponentialBackoff(), + isServiceFailure = defaultIsServiceFailure, } = options; let availabilityStatus: AvailabilityStatus = AVAILABILITY_STATUSES.Unknown; diff --git a/packages/network-controller/src/rpc-service/rpc-service.ts b/packages/network-controller/src/rpc-service/rpc-service.ts index c9904f43e6..c39f707763 100644 --- a/packages/network-controller/src/rpc-service/rpc-service.ts +++ b/packages/network-controller/src/rpc-service/rpc-service.ts @@ -67,10 +67,13 @@ export type RpcServiceOptions = { */ logger?: Pick; /** - * Options to pass to `createServicePolicy`. Note that `retryFilterPolicy` is - * not accepted, as it is overwritten. See {@link createServicePolicy}. + * Options to pass to `createServicePolicy`. Note that `retryFilterPolicy` and `isServiceFailure` + * are not accepted, as they are overwritten. See {@link createServicePolicy}. */ - policyOptions?: Omit; + policyOptions?: Omit< + CreateServicePolicyOptions, + 'retryFilterPolicy' | 'isServiceFailure' + >; /** * A function that checks if the user is currently offline. If it returns true, * connection errors will not be retried, preventing degraded and break @@ -372,6 +375,20 @@ export class RpcService { maxRetries: DEFAULT_MAX_RETRIES, maxConsecutiveFailures: DEFAULT_MAX_CONSECUTIVE_FAILURES, ...policyOptions, + isServiceFailure: (error) => { + if ( + typeof error === 'object' && + error !== null && + 'httpStatus' in error && + typeof error.httpStatus === 'number' + ) { + return error.httpStatus !== 429; + } + + // If the error is not an object, or doesn't have a numeric code property, + // consider it a service failure (e.g., network errors, timeouts, etc.) + return true; + }, retryFilterPolicy: handleWhen((error) => { // If user is offline, don't retry any errors // This prevents degraded/break callbacks from being triggered From 79ba16615801c7f2506809f30124452516ff9f46 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Mon, 15 Jun 2026 13:25:14 +0200 Subject: [PATCH 2/3] Use hasProperty --- packages/network-controller/src/rpc-service/rpc-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network-controller/src/rpc-service/rpc-service.ts b/packages/network-controller/src/rpc-service/rpc-service.ts index c39f707763..cf2e2d5ee9 100644 --- a/packages/network-controller/src/rpc-service/rpc-service.ts +++ b/packages/network-controller/src/rpc-service/rpc-service.ts @@ -379,7 +379,7 @@ export class RpcService { if ( typeof error === 'object' && error !== null && - 'httpStatus' in error && + hasProperty(error, 'httpStatus') && typeof error.httpStatus === 'number' ) { return error.httpStatus !== 429; From 534e5c671867b5566d6dcaaf655167ffbfb9e229 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Mon, 15 Jun 2026 14:24:54 +0200 Subject: [PATCH 3/3] Update CHANGELOG --- packages/controller-utils/CHANGELOG.md | 4 ++++ packages/network-controller/CHANGELOG.md | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/controller-utils/CHANGELOG.md b/packages/controller-utils/CHANGELOG.md index e20fed6015..4bce11d590 100644 --- a/packages/controller-utils/CHANGELOG.md +++ b/packages/controller-utils/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Allow overriding `isServiceFailure` in `createServicePolicy` ([#9123](https://github.com/MetaMask/core/pull/9123)) + ### Changed - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074)) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index fcff6929fd..f1863bafe0 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The constructor argument `isRpcFailoverEnabled` is no longer available. - `RemoteFeatureFlagController:stateChange` and `RemoteFeatureFlagController:getState` are now required. - Drop `async-mutex` dependency, which was no longer used in source ([#9064](https://github.com/MetaMask/core/pull/9064)) +- Consider all HTTP errors as service failures except `429` ([#9123](https://github.com/MetaMask/core/pull/9123)) ### Removed