11// SPDX-License-Identifier: MIT
2- // Copyright (c) 2025 The Pybricks Authors
2+ // Copyright (c) 2025-2026 The Pybricks Authors
33
44import { firmwareVersion } from '@pybricks/firmware' ;
55import { AnyAction } from 'redux' ;
@@ -37,6 +37,7 @@ import { pybricksHubCapabilitiesCharacteristicUUID } from '../ble-pybricks-servi
3737import { firmwareInstallPybricks } from '../firmware/actions' ;
3838import { RootState } from '../reducers' ;
3939import { assert , defined , ensureError , maybe } from '../utils' ;
40+ import { isLinux } from '../utils/os' ;
4041import { pythonVersionToSemver } from '../utils/version' ;
4142import {
4243 usbConnectPybricks ,
@@ -128,6 +129,7 @@ function* handleUsbConnectPybricks(hotPlugDevice?: USBDevice): Generator {
128129 return ;
129130 }
130131
132+ // TODO: show unexpected error message to user here
131133 console . error ( 'Failed to request USB device:' , reqDeviceErr ) ;
132134 yield * put ( usbDidFailToConnectPybricks ( ) ) ;
133135 yield * cleanup ( ) ;
@@ -140,13 +142,29 @@ function* handleUsbConnectPybricks(hotPlugDevice?: USBDevice): Generator {
140142 usbDevice = hotPlugDevice ;
141143 }
142144
143- const [ , openErr ] = yield * call ( ( ) => maybe ( usbDevice . open ( ) ) ) ;
144- if ( openErr ) {
145- // TODO: show error message to user here
146- console . error ( 'Failed to open USB device:' , openErr ) ;
147- yield * put ( usbDidFailToConnectPybricks ( ) ) ;
148- yield * cleanup ( ) ;
149- return ;
145+ for ( let retry = 0 ; ; retry ++ ) {
146+ const [ , openErr ] = yield * call ( ( ) => maybe ( usbDevice . open ( ) ) ) ;
147+ if ( openErr ) {
148+ // On Linux, the udev rules could still be processing, try a few
149+ // times before giving up.
150+ if ( isLinux ( ) && openErr . name === 'SecurityError' && retry < 5 ) {
151+ console . debug (
152+ `Retrying USB device open (${
153+ retry + 1
154+ } /5) after SecurityError on Linux`,
155+ ) ;
156+ yield * delay ( 100 ) ;
157+ continue ;
158+ }
159+
160+ // TODO: show error message to user here
161+ console . error ( 'Failed to open USB device:' , openErr ) ;
162+ yield * put ( usbDidFailToConnectPybricks ( ) ) ;
163+ yield * cleanup ( ) ;
164+ return ;
165+ }
166+
167+ break ;
150168 }
151169
152170 exitStack . push ( ( ) => usbDevice . close ( ) . catch ( console . debug ) ) ;
0 commit comments