From 368ab7cb965ea220cc1e8e4df29a9047bb5ec5af Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 24 Jan 2026 22:38:21 +0000 Subject: [PATCH] usb/sagas: Retry USB device open on Linux if SecurityError occurs On slow machines, udev rules may not have finished processing by the time we try to open the USB device. This causes a SecurityError, so we add a retry loop to handle this case. Closes: https://github.com/pybricks/support/issues/2372 --- src/usb/sagas.ts | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/usb/sagas.ts b/src/usb/sagas.ts index 80323e28..570df488 100644 --- a/src/usb/sagas.ts +++ b/src/usb/sagas.ts @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2025 The Pybricks Authors +// Copyright (c) 2025-2026 The Pybricks Authors import { firmwareVersion } from '@pybricks/firmware'; import { AnyAction } from 'redux'; @@ -128,6 +128,7 @@ function* handleUsbConnectPybricks(hotPlugDevice?: USBDevice): Generator { return; } + // TODO: show unexpected error message to user here console.error('Failed to request USB device:', reqDeviceErr); yield* put(usbDidFailToConnectPybricks()); yield* cleanup(); @@ -140,13 +141,27 @@ function* handleUsbConnectPybricks(hotPlugDevice?: USBDevice): Generator { usbDevice = hotPlugDevice; } - const [, openErr] = yield* call(() => maybe(usbDevice.open())); - if (openErr) { - // TODO: show error message to user here - console.error('Failed to open USB device:', openErr); - yield* put(usbDidFailToConnectPybricks()); - yield* cleanup(); - return; + for (let retry = 1; ; retry++) { + const [, openErr] = yield* call(() => maybe(usbDevice.open())); + if (openErr) { + // On Linux/Android, the udev rules could still be processing, try + // a few times before giving up. + if (openErr.name === 'SecurityError' && retry <= 5) { + console.debug( + `Retrying USB device open (${retry}/5) after SecurityError on Linux`, + ); + yield* delay(100); + continue; + } + + // TODO: show error message to user here + console.error('Failed to open USB device:', openErr); + yield* put(usbDidFailToConnectPybricks()); + yield* cleanup(); + return; + } + + break; } exitStack.push(() => usbDevice.close().catch(console.debug));