From 1fe7a13e9e4ef36cc68e7eb3cff1e56dfba01e83 Mon Sep 17 00:00:00 2001 From: Cedric Karungu Date: Tue, 6 Oct 2026 17:51:25 +0200 Subject: [PATCH] Clarify non-fatal React Native DevTools preparation errors When preparing the standalone React Native DevTools shell throws (e.g. the Electron binary fails to launch on some Linux setups), the dev server logged "An unknown error occurred while installing React Native DevTools.." as an error. This is not fatal: /open-debugger falls back to a browser window. - Give the catch-all unexpected_error result a humanReadableMessage that says a fallback is used, consistent with the other failure codes. - Log unexpected_error as a warning, like the other non-fatal failure codes, and drop the duplicated trailing period. Addresses #54906 --- .../__tests__/prepareDebuggerShell-test.js | 30 ++++++++++++++++ .../debugger-shell/src/node/index.flow.js | 3 ++ .../__tests__/StandaloneFuseboxShell-test.js | 34 +++++++++++++++++++ .../dev-middleware/src/createDevMiddleware.js | 9 ++--- 4 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 packages/debugger-shell/src/node/__tests__/prepareDebuggerShell-test.js diff --git a/packages/debugger-shell/src/node/__tests__/prepareDebuggerShell-test.js b/packages/debugger-shell/src/node/__tests__/prepareDebuggerShell-test.js new file mode 100644 index 000000000000..bfdb285109dd --- /dev/null +++ b/packages/debugger-shell/src/node/__tests__/prepareDebuggerShell-test.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +jest.mock('../private/LaunchUtils', () => ({ + prepareDebuggerShellFromDotSlashFile: jest.fn(() => + Promise.reject(new Error('spawn failed')), + ), +})); + +const {unstable_prepareDebuggerShell} = require('../../'); + +describe('unstable_prepareDebuggerShell', () => { + test('returns a human readable message for unexpected errors', async () => { + const result = await unstable_prepareDebuggerShell({flavor: 'prebuilt'}); + + expect(result.code).toBe('unexpected_error'); + expect(result.verboseInfo).toBe('spawn failed'); + expect(result.humanReadableMessage).toContain( + 'Using a fallback version instead', + ); + }); +}); diff --git a/packages/debugger-shell/src/node/index.flow.js b/packages/debugger-shell/src/node/index.flow.js index 60140b75fcc3..0d3c8ac17ef2 100644 --- a/packages/debugger-shell/src/node/index.flow.js +++ b/packages/debugger-shell/src/node/index.flow.js @@ -175,6 +175,9 @@ async function unstable_prepareDebuggerShell({ } catch (e) { return { code: 'unexpected_error', + humanReadableMessage: + 'An unexpected error occurred while preparing React Native DevTools. ' + + 'Using a fallback version instead.', verboseInfo: e.message, }; } diff --git a/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js b/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js index 18f45b3f97e3..18da9d2a24a6 100644 --- a/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js +++ b/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js @@ -232,4 +232,38 @@ describe('enableStandaloneFuseboxShell experiment', () => { }); }); }); + + describe('preparation failure logging', () => { + const logger = { + error: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + }; + const {ToolLauncherWithFuseboxShell} = setupToolLauncherWithFuseboxShell( + () => + Promise.resolve({ + code: 'unexpected_error', + verboseInfo: 'LaunchProcess: failed to execvp', + }), + ); + const server = withServerForEachTest({ + logger, + unstable_toolLauncher: ToolLauncherWithFuseboxShell, + unstable_experiments: { + enableStandaloneFuseboxShell: true, + }, + }); + + test('logs an unexpected error as a non-fatal warning', async () => { + // Access the server to ensure the dev middleware has been created + expect(server.serverBaseUrl).toBeDefined(); + await jest.runOnlyPendingTimersAsync(); + + expect(logger.error).not.toHaveBeenCalled(); + expect(logger.warn).toHaveBeenCalledTimes(1); + const [message] = logger.warn.mock.calls[0]; + expect(message).toContain('Using a fallback version instead'); + expect(message).toContain('LaunchProcess: failed to execvp'); + }); + }); }); diff --git a/packages/dev-middleware/src/createDevMiddleware.js b/packages/dev-middleware/src/createDevMiddleware.js index f5f86461da8c..3cddcf75fa63 100644 --- a/packages/dev-middleware/src/createDevMiddleware.js +++ b/packages/dev-middleware/src/createDevMiddleware.js @@ -177,15 +177,16 @@ function createWrappedEventReporter( case 'not_implemented': break; case 'unexpected_error': { + // Preparation failures are not fatal: the debugger falls back to + // opening in a browser window, so log a warning, not an error. let message = event.result.humanReadableMessage ?? - 'An unknown error occurred while installing React Native DevTools.'; + 'An unknown error occurred while installing React Native DevTools. ' + + 'Using a fallback version instead.'; if (event.result.verboseInfo != null) { message += ` Details:\n\n${event.result.verboseInfo}`; - } else { - message += '.'; } - logger?.error(message); + logger?.warn(message); break; } case 'possible_corruption':