From 58d2a8c27903a5364cc160db3a3e8b77bc3d6040 Mon Sep 17 00:00:00 2001 From: Maciej Lesniewski Date: Thu, 16 Apr 2026 22:34:11 +0200 Subject: [PATCH 1/8] feat: add backoff constants and helpers for retry delay Co-Authored-By: Claude Opus 4.6 --- lib/OnyxUtils.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 8df270db4..9f97ff55d 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -64,6 +64,24 @@ const STORAGE_ERRORS = [...IDB_STORAGE_ERRORS, ...SQLITE_STORAGE_ERRORS]; // Max number of retries for failed storage operations const MAX_STORAGE_OPERATION_RETRY_ATTEMPTS = 5; +// Connection/state errors where the DB needs time to recover — backoff helps, eviction does not +const IDB_CONNECTION_ERRORS = [ + 'internal error opening backing store', // Chrome/Edge: corrupted IDB state + 'connection to indexed database server lost', // Safari: IDB connection dropped + 'the database connection is closing', // Cross-browser: DB closing during write +] as const; + +const SQLITE_CONNECTION_ERRORS = [ + 'disk i/o error', // Native: filesystem/device stress + 'database is locked', // Native: concurrent access contention +] as const; + +const CONNECTION_ERRORS = [...IDB_CONNECTION_ERRORS, ...SQLITE_CONNECTION_ERRORS]; + +// Retry backoff configuration +const RETRY_BASE_DELAY_MS = 100; +const RETRY_JITTER_FACTOR = 0.25; + type OnyxMethod = ValueOf; // Key/value store of Onyx key and arrays of values to merge @@ -763,6 +781,26 @@ function remove(key: TKey, isProcessingCollectionUpdate?: return Storage.removeItem(key).then(() => undefined); } +/** + * Returns a promise that resolves after the given number of milliseconds. + */ +function wait(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +/** + * Calculates exponential backoff delay with jitter for a given retry attempt. + * Formula: baseDelay * 2^attempt ± jitter + * Attempt 0: ~100ms, Attempt 1: ~200ms, ..., Attempt 4: ~1600ms + */ +function getRetryDelay(attempt: number): number { + const baseDelay = RETRY_BASE_DELAY_MS * 2 ** attempt; + const jitter = baseDelay * RETRY_JITTER_FACTOR * (2 * Math.random() - 1); + return Math.round(baseDelay + jitter); +} + function reportStorageQuota(error?: Error): Promise { return Storage.getDatabaseSize() .then(({bytesUsed, bytesRemaining}) => { From 16aef2e62d14e4b028618f61d3291dc99c5a752d Mon Sep 17 00:00:00 2001 From: Maciej Lesniewski Date: Thu, 16 Apr 2026 22:35:11 +0200 Subject: [PATCH 2/8] feat: add exponential backoff delay to non-capacity error retries Errors like lost IDB connections, closing databases, and backing store failures now wait with exponential backoff (100ms * 2^attempt +/- 25% jitter) before retrying, giving the DB connection time to recover. Capacity errors (QuotaExceeded, disk full) keep immediate retry with eviction. Co-Authored-By: Claude Opus 4.6 --- lib/OnyxUtils.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 9f97ff55d..1332fd7cd 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -838,8 +838,15 @@ function retryOperation(error: Error, on } if (!isStorageCapacityError) { + const delay = getRetryDelay(currentRetryAttempt); + const isConnectionError = CONNECTION_ERRORS.some((connError) => errorName?.includes(connError) || errorMessage?.includes(connError)); + + if (isConnectionError) { + Logger.logInfo(`Connection error detected, retrying with backoff (${delay}ms). Error: ${error}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`); + } + // @ts-expect-error No overload matches this call. - return onyxMethod(defaultParams, nextRetryAttempt); + return wait(delay).then(() => onyxMethod(defaultParams, nextRetryAttempt)); } // Find the least recently accessed evictable key that we can remove From bf9cf5f7bb0e8ce879edfdd451d37d670d5d9a52 Mon Sep 17 00:00:00 2001 From: Maciej Lesniewski Date: Thu, 16 Apr 2026 23:10:42 +0200 Subject: [PATCH 3/8] test: update retry tests for backoff and add backoff-specific tests Existing retry tests now use fake timers to handle backoff delays. New tests verify: exponential delay progression, connection error logging, and capacity errors remaining immediate. Co-Authored-By: Claude Opus 4.6 --- tests/unit/onyxUtilsTest.ts | 96 +++++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/tests/unit/onyxUtilsTest.ts b/tests/unit/onyxUtilsTest.ts index f16cf7a74..1af0af004 100644 --- a/tests/unit/onyxUtilsTest.ts +++ b/tests/unit/onyxUtilsTest.ts @@ -432,21 +432,35 @@ describe('OnyxUtils', () => { const diskFullError = new Error('database or disk is full'); it('should retry only one time if the operation is firstly failed and then passed', async () => { - StorageMock.setItem = jest.fn(StorageMock.setItem).mockRejectedValueOnce(genericError).mockImplementation(StorageMock.setItem); + jest.useFakeTimers(); + try { + StorageMock.setItem = jest.fn(StorageMock.setItem).mockRejectedValueOnce(genericError).mockImplementation(StorageMock.setItem); - await Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + const setPromise = Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + await jest.runAllTimersAsync(); + await setPromise; - // Should be called once, since Storage.setItem if failed only once - expect(retryOperationSpy).toHaveBeenCalledTimes(1); + // Should be called once, since Storage.setItem failed only once + expect(retryOperationSpy).toHaveBeenCalledTimes(1); + } finally { + jest.useRealTimers(); + } }); it('should stop retrying after MAX_STORAGE_OPERATION_RETRY_ATTEMPTS retries for failing operation', async () => { - StorageMock.setItem = jest.fn().mockRejectedValue(genericError); + jest.useFakeTimers(); + try { + StorageMock.setItem = jest.fn().mockRejectedValue(genericError); - await Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + const setPromise = Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + await jest.runAllTimersAsync(); + await setPromise; - // Should be called 6 times: initial attempt + 5 retries (MAX_STORAGE_OPERATION_RETRY_ATTEMPTS) - expect(retryOperationSpy).toHaveBeenCalledTimes(6); + // Should be called 6 times: initial attempt + 5 retries (MAX_STORAGE_OPERATION_RETRY_ATTEMPTS) + expect(retryOperationSpy).toHaveBeenCalledTimes(6); + } finally { + jest.useRealTimers(); + } }); it("should throw error for if operation failed with \"Failed to execute 'put' on 'IDBObjectStore': invalid data\" error", async () => { @@ -512,6 +526,72 @@ describe('OnyxUtils', () => { await OnyxUtils.remove(evictableKey); expect(OnyxCache.getKeyForEviction()).toBeUndefined(); }); + + it('should apply exponential backoff delay for non-capacity errors', async () => { + jest.useFakeTimers(); + try { + const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + StorageMock.setItem = jest.fn().mockRejectedValue(genericError); + + const setPromise = Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + await jest.runAllTimersAsync(); + await setPromise; + + // Filter setTimeout calls to only those from our wait() helper (delay > 0) + const backoffDelays = setTimeoutSpy.mock.calls + .map((call) => call[1]) + .filter((delay): delay is number => typeof delay === 'number' && delay > 0); + + // Should have 5 backoff delays (one before each of the 5 retries, attempts 0-4) + // The 6th call to retryOperation (attempt 5) hits the MAX check and resolves without waiting + expect(backoffDelays).toHaveLength(5); + + // Verify exponential growth pattern: each delay should be roughly double the previous + // With ±25% jitter, delay[n+1] / delay[n] should be between ~1.2 and ~3.3 + for (let i = 1; i < backoffDelays.length; i++) { + const ratio = backoffDelays[i] / backoffDelays[i - 1]; + expect(ratio).toBeGreaterThan(1.0); + expect(ratio).toBeLessThan(4.0); + } + + setTimeoutSpy.mockRestore(); + } finally { + jest.useRealTimers(); + } + }); + + it('should log connection error with backoff delay info', async () => { + jest.useFakeTimers(); + try { + const logInfoSpy = jest.spyOn(Logger, 'logInfo'); + const connectionError = new Error('Connection to Indexed Database server lost. Refresh the page to try again'); + StorageMock.setItem = jest.fn(StorageMock.setItem).mockRejectedValueOnce(connectionError).mockImplementation(StorageMock.setItem); + + const setPromise = Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + await jest.runAllTimersAsync(); + await setPromise; + + expect(logInfoSpy).toHaveBeenCalledWith(expect.stringContaining('Connection error detected, retrying with backoff')); + expect(logInfoSpy).toHaveBeenCalledWith(expect.stringContaining('Connection to Indexed Database server lost')); + } finally { + jest.useRealTimers(); + } + }); + + it('should NOT apply backoff delay for capacity errors (immediate retry with eviction)', async () => { + const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + StorageMock.setItem = jest.fn().mockRejectedValue(diskFullError); + + await Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + + // Capacity errors should not trigger any backoff delays (delay > 0) + const backoffDelays = setTimeoutSpy.mock.calls + .map((call) => call[1]) + .filter((delay): delay is number => typeof delay === 'number' && delay > 0); + + expect(backoffDelays).toHaveLength(0); + setTimeoutSpy.mockRestore(); + }); }); describe('storage eviction', () => { From bb8e0c290c7da52ed8c6ff735b3855b21b758360 Mon Sep 17 00:00:00 2001 From: Maciej Lesniewski Date: Thu, 16 Apr 2026 23:30:50 +0200 Subject: [PATCH 4/8] fix: guard getRetryDelay against negative values Co-Authored-By: Claude Opus 4.6 --- lib/OnyxUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 1332fd7cd..9dd8a13ba 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -798,7 +798,7 @@ function wait(ms: number): Promise { function getRetryDelay(attempt: number): number { const baseDelay = RETRY_BASE_DELAY_MS * 2 ** attempt; const jitter = baseDelay * RETRY_JITTER_FACTOR * (2 * Math.random() - 1); - return Math.round(baseDelay + jitter); + return Math.max(0, Math.round(baseDelay + jitter)); } function reportStorageQuota(error?: Error): Promise { From 0bb122791a53dd7e5ce04ef734dc81c7a4c94214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Le=C5=9Bniewski?= Date: Thu, 30 Apr 2026 19:30:12 +0200 Subject: [PATCH 5/8] Add observability logging for backoff experiment outcomes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recovery and exhaustion logs needed to measure whether backoff actually helps connection errors recover. Without these, we can only see retry attempts but not outcomes — making the experiment unmeasurable. - Log recovery success with attempt number when connection error resolves after backoff - Classify exhaustion logs: connection errors get distinct message from generic failures - Extract isConnectionError earlier so both exhaustion and retry paths can use it - Add 2 new tests for recovery and exhaustion logging Co-Authored-By: Claude Opus 4.6 --- lib/OnyxUtils.ts | 18 ++++++++++++++---- tests/unit/onyxUtilsTest.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 9dd8a13ba..8028ecab9 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -831,22 +831,32 @@ function retryOperation(error: Error, on const errorMessage = error?.message?.toLowerCase?.(); const errorName = error?.name?.toLowerCase?.(); const isStorageCapacityError = STORAGE_ERRORS.some((storageError) => errorName?.includes(storageError) || errorMessage?.includes(storageError)); + const isConnectionError = CONNECTION_ERRORS.some((connError) => errorName?.includes(connError) || errorMessage?.includes(connError)); if (nextRetryAttempt > MAX_STORAGE_OPERATION_RETRY_ATTEMPTS) { - Logger.logAlert(`Storage operation failed after 5 retries. Error: ${error}. onyxMethod: ${onyxMethod.name}.`); + if (isConnectionError) { + Logger.logAlert(`Connection error exhausted all retries with backoff. Error: ${error}. onyxMethod: ${onyxMethod.name}.`); + } else { + Logger.logAlert(`Storage operation failed after 5 retries. Error: ${error}. onyxMethod: ${onyxMethod.name}.`); + } return Promise.resolve(); } if (!isStorageCapacityError) { const delay = getRetryDelay(currentRetryAttempt); - const isConnectionError = CONNECTION_ERRORS.some((connError) => errorName?.includes(connError) || errorMessage?.includes(connError)); if (isConnectionError) { Logger.logInfo(`Connection error detected, retrying with backoff (${delay}ms). Error: ${error}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`); } - // @ts-expect-error No overload matches this call. - return wait(delay).then(() => onyxMethod(defaultParams, nextRetryAttempt)); + return wait(delay).then(() => + // @ts-expect-error No overload matches this call. + onyxMethod(defaultParams, nextRetryAttempt).then(() => { + if (isConnectionError) { + Logger.logInfo(`Connection error recovered after backoff on attempt ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}. onyxMethod: ${onyxMethod.name}.`); + } + }), + ); } // Find the least recently accessed evictable key that we can remove diff --git a/tests/unit/onyxUtilsTest.ts b/tests/unit/onyxUtilsTest.ts index 1af0af004..967ff228f 100644 --- a/tests/unit/onyxUtilsTest.ts +++ b/tests/unit/onyxUtilsTest.ts @@ -578,6 +578,40 @@ describe('OnyxUtils', () => { } }); + it('should log recovery when connection error succeeds after backoff', async () => { + jest.useFakeTimers(); + try { + const logInfoSpy = jest.spyOn(Logger, 'logInfo'); + const connectionError = new Error('Connection to Indexed Database server lost. Refresh the page to try again'); + StorageMock.setItem = jest.fn(StorageMock.setItem).mockRejectedValueOnce(connectionError).mockImplementation(StorageMock.setItem); + + const setPromise = Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + await jest.runAllTimersAsync(); + await setPromise; + + expect(logInfoSpy).toHaveBeenCalledWith(expect.stringContaining('Connection error recovered after backoff on attempt')); + } finally { + jest.useRealTimers(); + } + }); + + it('should log connection-specific exhaustion message when all retries fail', async () => { + jest.useFakeTimers(); + try { + const logAlertSpy = jest.spyOn(Logger, 'logAlert'); + const connectionError = new Error('Connection to Indexed Database server lost. Refresh the page to try again'); + StorageMock.setItem = jest.fn().mockRejectedValue(connectionError); + + const setPromise = Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); + await jest.runAllTimersAsync(); + await setPromise; + + expect(logAlertSpy).toHaveBeenCalledWith(expect.stringContaining('Connection error exhausted all retries with backoff')); + } finally { + jest.useRealTimers(); + } + }); + it('should NOT apply backoff delay for capacity errors (immediate retry with eviction)', async () => { const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); StorageMock.setItem = jest.fn().mockRejectedValue(diskFullError); From 376a6bad256fe1ccb02b62c3dd328fa8ceccf642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Le=C5=9Bniewski?= Date: Fri, 1 May 2026 20:44:55 +0200 Subject: [PATCH 6/8] Fix CI failures: lint early-return, undefined .then(), rebuild docs - Wrap onyxMethod return with Promise.resolve() to handle undefined returns (fixes perf-test TypeError: Cannot read properties of undefined) - Use early-return guard for non-connection errors (fixes prefer-early-return lint) - Rebuild API-INTERNAL.md (fixes verify check) Co-Authored-By: Claude Opus 4.6 --- API-INTERNAL.md | 22 ++++++++++++++++++++++ lib/OnyxUtils.ts | 15 +++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/API-INTERNAL.md b/API-INTERNAL.md index 10b70448f..0909a4b89 100644 --- a/API-INTERNAL.md +++ b/API-INTERNAL.md @@ -78,6 +78,14 @@ If the requested key is a collection, it will return an object with all the coll
remove()

Remove a key from Onyx and update the subscribers

+
wait()
+

Returns a promise that resolves after the given number of milliseconds.

+
+
getRetryDelay()
+

Calculates exponential backoff delay with jitter for a given retry attempt. +Formula: baseDelay * 2^attempt ± jitter +Attempt 0: ~100ms, Attempt 1: ~200ms, ..., Attempt 4: ~1600ms

+
retryOperation()

Handles storage operation failures based on the error type:

    @@ -337,6 +345,20 @@ Gets the data for a given an array of matching keys, combines them into an objec ## remove() Remove a key from Onyx and update the subscribers +**Kind**: global function + + +## wait() +Returns a promise that resolves after the given number of milliseconds. + +**Kind**: global function + + +## getRetryDelay() +Calculates exponential backoff delay with jitter for a given retry attempt. +Formula: baseDelay * 2^attempt ± jitter +Attempt 0: ~100ms, Attempt 1: ~200ms, ..., Attempt 4: ~1600ms + **Kind**: global function diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 8028ecab9..6556d6a83 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -849,14 +849,13 @@ function retryOperation(error: Error, on Logger.logInfo(`Connection error detected, retrying with backoff (${delay}ms). Error: ${error}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`); } - return wait(delay).then(() => - // @ts-expect-error No overload matches this call. - onyxMethod(defaultParams, nextRetryAttempt).then(() => { - if (isConnectionError) { - Logger.logInfo(`Connection error recovered after backoff on attempt ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}. onyxMethod: ${onyxMethod.name}.`); - } - }), - ); + // @ts-expect-error No overload matches this call. + return wait(delay).then(() => Promise.resolve(onyxMethod(defaultParams, nextRetryAttempt)).then(() => { + if (!isConnectionError) { + return; + } + Logger.logInfo(`Connection error recovered after backoff on attempt ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}. onyxMethod: ${onyxMethod.name}.`); + })); } // Find the least recently accessed evictable key that we can remove From 2430f84158e623c09aec8c90144a16a7fc0f38b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Le=C5=9Bniewski?= Date: Fri, 1 May 2026 20:47:43 +0200 Subject: [PATCH 7/8] Run prettier on changed files Co-Authored-By: Claude Opus 4.6 --- lib/OnyxUtils.ts | 18 +++++++++++------- tests/unit/onyxUtilsTest.ts | 8 ++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 6556d6a83..76a01b869 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -846,16 +846,20 @@ function retryOperation(error: Error, on const delay = getRetryDelay(currentRetryAttempt); if (isConnectionError) { - Logger.logInfo(`Connection error detected, retrying with backoff (${delay}ms). Error: ${error}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`); + Logger.logInfo( + `Connection error detected, retrying with backoff (${delay}ms). Error: ${error}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`, + ); } // @ts-expect-error No overload matches this call. - return wait(delay).then(() => Promise.resolve(onyxMethod(defaultParams, nextRetryAttempt)).then(() => { - if (!isConnectionError) { - return; - } - Logger.logInfo(`Connection error recovered after backoff on attempt ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}. onyxMethod: ${onyxMethod.name}.`); - })); + return wait(delay).then(() => + Promise.resolve(onyxMethod(defaultParams, nextRetryAttempt)).then(() => { + if (!isConnectionError) { + return; + } + Logger.logInfo(`Connection error recovered after backoff on attempt ${nextRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}. onyxMethod: ${onyxMethod.name}.`); + }), + ); } // Find the least recently accessed evictable key that we can remove diff --git a/tests/unit/onyxUtilsTest.ts b/tests/unit/onyxUtilsTest.ts index 967ff228f..e0c35d2f9 100644 --- a/tests/unit/onyxUtilsTest.ts +++ b/tests/unit/onyxUtilsTest.ts @@ -538,9 +538,7 @@ describe('OnyxUtils', () => { await setPromise; // Filter setTimeout calls to only those from our wait() helper (delay > 0) - const backoffDelays = setTimeoutSpy.mock.calls - .map((call) => call[1]) - .filter((delay): delay is number => typeof delay === 'number' && delay > 0); + const backoffDelays = setTimeoutSpy.mock.calls.map((call) => call[1]).filter((delay): delay is number => typeof delay === 'number' && delay > 0); // Should have 5 backoff delays (one before each of the 5 retries, attempts 0-4) // The 6th call to retryOperation (attempt 5) hits the MAX check and resolves without waiting @@ -619,9 +617,7 @@ describe('OnyxUtils', () => { await Onyx.set(ONYXKEYS.TEST_KEY, {test: 'data'}); // Capacity errors should not trigger any backoff delays (delay > 0) - const backoffDelays = setTimeoutSpy.mock.calls - .map((call) => call[1]) - .filter((delay): delay is number => typeof delay === 'number' && delay > 0); + const backoffDelays = setTimeoutSpy.mock.calls.map((call) => call[1]).filter((delay): delay is number => typeof delay === 'number' && delay > 0); expect(backoffDelays).toHaveLength(0); setTimeoutSpy.mockRestore(); From bc0b381ddb73987c291c196f48cb8086faf42d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Le=C5=9Bniewski?= Date: Fri, 1 May 2026 20:51:10 +0200 Subject: [PATCH 8/8] Move ts-expect-error to correct line inside Promise.resolve callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The directive was on the wrong line after restructuring — TS saw it as unused since the actual type error is inside the .then() callback. Co-Authored-By: Claude Opus 4.6 --- lib/OnyxUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 76a01b869..4f4e29740 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -851,8 +851,8 @@ function retryOperation(error: Error, on ); } - // @ts-expect-error No overload matches this call. return wait(delay).then(() => + // @ts-expect-error No overload matches this call. Promise.resolve(onyxMethod(defaultParams, nextRetryAttempt)).then(() => { if (!isConnectionError) { return;