From acdfd1ea2bb3032cdc3929e739cf7a6f61583176 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Mon, 22 Jun 2026 17:36:32 -0400 Subject: [PATCH] fix(browserbase): surface the underlying cause on exhausted retries When retries are exhausted, withBrowserbaseRetry threw a generic "Browserbase is temporarily unavailable" that hid the real upstream error. Append the underlying error text to the message so an exhausted retry is diagnosable from the UI/response, not only the server logs. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_013zSwXMqVNvWLJBZEot9x12 --- .../browserbase-session.service.spec.ts | 16 ++++++++++++++++ .../browserbase/browserbase-session.service.ts | 4 +++- .../browserbase-upstream-error.spec.ts | 8 ++++++++ .../browserbase/browserbase-upstream-error.ts | 6 ++++-- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/apps/api/src/browserbase/browserbase-session.service.spec.ts b/apps/api/src/browserbase/browserbase-session.service.spec.ts index de2cc2059..b95f2d3f4 100644 --- a/apps/api/src/browserbase/browserbase-session.service.spec.ts +++ b/apps/api/src/browserbase/browserbase-session.service.spec.ts @@ -114,6 +114,22 @@ describe('BrowserbaseSessionService', () => { expect(createContext).toHaveBeenCalledTimes(3); }); + it('includes the underlying cause in the exhausted-retry message', async () => { + jest.useFakeTimers(); + const service = new BrowserbaseSessionService(); + const createContext = jest.fn().mockRejectedValue(prematureCloseError()); + jest + .spyOn(service, 'getBrowserbase') + .mockReturnValue(mockBrowserbaseClient({ createContext })); + + const promise = service.createBrowserbaseContext().catch((error) => error); + await jest.advanceTimersByTimeAsync(1_000); + const error = await promise; + + expect(error).toBeInstanceOf(ServiceUnavailableException); + expect(error.message).toContain('Premature close'); + }); + it('preserves non-retryable Browserbase failures', async () => { const service = new BrowserbaseSessionService(); const browserbaseError = Object.assign( diff --git a/apps/api/src/browserbase/browserbase-session.service.ts b/apps/api/src/browserbase/browserbase-session.service.ts index 24485d719..b2accbd61 100644 --- a/apps/api/src/browserbase/browserbase-session.service.ts +++ b/apps/api/src/browserbase/browserbase-session.service.ts @@ -143,7 +143,9 @@ export class BrowserbaseSessionService { attempt, error: getBrowserbaseErrorText(error), }); - throw browserbaseUnavailableException(); + // Surface the underlying cause in the message so an exhausted retry + // is diagnosable from the UI/response, not just the server logs. + throw browserbaseUnavailableException(getBrowserbaseErrorText(error)); } this.logger.warn(`Browserbase ${operationName} failed; retrying`, { diff --git a/apps/api/src/browserbase/browserbase-upstream-error.spec.ts b/apps/api/src/browserbase/browserbase-upstream-error.spec.ts index 0cb3eaa28..fd29978b8 100644 --- a/apps/api/src/browserbase/browserbase-upstream-error.spec.ts +++ b/apps/api/src/browserbase/browserbase-upstream-error.spec.ts @@ -26,4 +26,12 @@ describe('browserbase upstream errors', () => { 'Browserbase is temporarily unavailable. Please retry in a moment.', ); }); + + it('appends the underlying cause when provided', () => { + const error = browserbaseUnavailableException('Premature close'); + + expect(error.message).toBe( + 'Browserbase is temporarily unavailable. Please retry in a moment. (Premature close)', + ); + }); }); diff --git a/apps/api/src/browserbase/browserbase-upstream-error.ts b/apps/api/src/browserbase/browserbase-upstream-error.ts index ed054baa8..124d3fe01 100644 --- a/apps/api/src/browserbase/browserbase-upstream-error.ts +++ b/apps/api/src/browserbase/browserbase-upstream-error.ts @@ -65,7 +65,9 @@ export const isRetryableBrowserbaseUpstreamError = ( return RETRYABLE_MESSAGE_PARTS.some((part) => message.includes(part)); }; -export const browserbaseUnavailableException = () => +export const browserbaseUnavailableException = (detail?: string) => new ServiceUnavailableException( - 'Browserbase is temporarily unavailable. Please retry in a moment.', + detail + ? `Browserbase is temporarily unavailable. Please retry in a moment. (${detail})` + : 'Browserbase is temporarily unavailable. Please retry in a moment.', );