don't sys.exit() from run_offline_command, this leads to wait on lock that never releases.
What changed, and why it matters
This commit fixes a bug where Electrum's command-line tool would call sys.exit() deep inside an offline command, causing the program to hang while waiting for a lock that never gets released. The fix replaces those abrupt exits with proper exception handling so the caller can clean up and exit cleanly. It is a reliability/bug-fix change rather than a security vulnerability in the traditional sense, though the hang could be annoying or disruptive.
No immediate security action required; treat as a normal bug-fix update. Users running Electrum offline CLI commands with hardware wallets or password-protected wallets may benefit from updating to avoid hangs.
Security signals we found
Fixes process hang caused by sys.exit() inside command path
Replaces hard exits with exception propagation for proper cleanup
Adds explicit exception handling for InvalidPassword and UserCancelled at command boundary
Evidence from the diff
The patch removes sys.exit() calls from get_password_for_hw_device_encrypted_storage and run_offline_command in run_electrum, replacing them with raised exceptions (UserFacingException, re-raised UserCancelled, and re-raised InvalidPassword). A new exception handler in handle_cmd catches InvalidPassword and UserCancelled, prints a message, and calls sys_exit(1). This prevents the process from terminating inside code paths that hold or are expected to release locks, which previously caused a hang waiting for a lock that would never be released.
Changed components
run_electrum CLI entrypointrun_offline_commandget_password_for_hw_device_encrypted_storagehandle_cmdInspect captured patch +9 / −4
diff --git a/run_electrum b/run_electrum
index 8e525ab..08038ce 100755
--- a/run_electrum
+++ b/run_electrum
@@ -211,8 +211,7 @@ def get_connected_hw_devices(plugins: 'Plugins'):
def get_password_for_hw_device_encrypted_storage(plugins: 'Plugins') -> str:
devices = get_connected_hw_devices(plugins)
if len(devices) == 0:
- print_msg("Error: No connected hw device found. Cannot decrypt this wallet.")
- sys.exit(1)
+ raise UserFacingException("Error: No connected hw device found. Cannot decrypt this wallet.")
elif len(devices) > 1:
print_msg("Warning: multiple hardware devices detected. "
"The first one will be used to decrypt the wallet.")
@@ -224,7 +223,7 @@ def get_password_for_hw_device_encrypted_storage(plugins: 'Plugins') -> str:
client.handler = client.plugin.create_handler(None)
return client.get_password_for_storage_encryption()
except UserCancelled:
- sys.exit(0)
+ raise
async def run_offline_command(config: 'SimpleConfig', config_options: dict, wallet_path: str, plugins: 'Plugins'):
@@ -251,7 +250,7 @@ async def run_offline_command(config: 'SimpleConfig', config_options: dict, wall
wallet.check_password(password)
except InvalidPassword:
print_msg("Error: This password does not decode this wallet.")
- sys.exit(1)
+ raise
if cmd.requires_network:
print_msg("Warning: running command offline")
# arguments passed to function
@@ -592,6 +591,12 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
except UserFacingException as e:
print_stderr(str(e))
sys_exit(1)
+ except InvalidPassword:
+ print_stderr("Invalid password")
+ sys_exit(1)
+ except UserCancelled:
+ print_stderr("Aborted by user")
+ sys_exit(1)
except Exception as e:
_logger.exception("error running command (without daemon)")
sys_exit(1)
Why this scored 24/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.