Skip to content

fix: Ledger "Something went wrong" error on reconnect during send/sign#28316

Closed
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-ledger-error-on-signing
Closed

fix: Ledger "Something went wrong" error on reconnect during send/sign#28316
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-ledger-error-on-signing

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 2, 2026

After a Ledger disconnects mid-signing and the user reconnects, tapping "Continue" shows "Something went wrong" instead of proceeding to sign. Three related bugs compound to cause this.

Root causes & fixes

useDeviceConnectionFlow — wrong retry path when state.deviceId is null

retryEnsureDeviceReady relies on state.deviceId to know which device to reconnect. If the initial BLE open failed before DeviceEvent.Connected fired (device already off when Confirm was tapped, or fresh app launch), state.deviceId is never populated and retry silently falls into scanning mode. A scan failure with an unrecognised error code then surfaces as ErrorCode.Unknown → "Something went wrong".

Fix: Add pendingTargetDeviceIdRef — set from targetDeviceId at the top of ensureDeviceReady. retryEnsureDeviceReady now uses deviceId ?? pendingTargetDeviceIdRef.current so it always reconnects directly to the correct device.

// Before
if (deviceId && adapter) { ... } else { /* falls to scanning */ }

// After
const retryDeviceId = deviceId ?? pendingTargetDeviceIdRef.current;
if (retryDeviceId && adapter) { ... } else { /* scanning only as true last resort */ }

LedgerBluetoothAdapter#doEnsureDeviceReady — silent return false on null transport

When the transport is cleared by a disconnect event immediately after connect(), the method returned false with no error, leaving the UI stuck at "Connecting" indefinitely.

Fix: Throw a named DisconnectedDevice error so the retry loop in ensureDeviceReady handles it as transient and retries automatically.

LedgerBluetoothAdapter#verifyEthereumAppUnlocked — unrecognised error surfaced as "Something went wrong"

throw new Error('Transport not available') uses a generic name/message that matches no entry in ERROR_NAME_MAPPINGS and no pattern in parseErrorByMessage, so parseErrorByType falls through to ErrorCode.Unknown.

Fix: Throw a named DisconnectedDeviceDuringOperation error — transient (auto-retried up to 3×) and maps to ErrorCode.DeviceDisconnected if all retries fail, giving the user an actionable message.


Note

Medium Risk
Changes control flow and error classification in Ledger connection/readiness checks; could alter retry behavior and surfaced errors during BLE instability or app-switching.

Overview
Fixes Ledger BLE reconnect flows so retrying after a mid-sign disconnect resumes signing instead of falling back to scanning or surfacing a generic "Something went wrong".

useDeviceConnectionFlow now remembers the last deviceId passed to ensureDeviceReady and uses it in retryEnsureDeviceReady when state.deviceId is still null, and clears that stored ID on closeFlow. LedgerBluetoothAdapter now throws named disconnect errors when #transport is unexpectedly null after connect() or during Ethereum-app verification so the existing retry loop treats these as transient (tests updated accordingly).

Reviewed by Cursor Bugbot for commit aa97022. Bugbot is set up for automated code reviews on this repo. Configure here.

Three related bugs fixed:

1. useDeviceConnectionFlow: retryEnsureDeviceReady now uses the device ID
   stored in pendingTargetDeviceIdRef (set at the start of ensureDeviceReady)
   when state.deviceId is null. Previously, if the initial BLE open failed
   before DeviceEvent.Connected could set state.deviceId (or on a fresh app
   launch), the retry fell through to scanning mode instead of connecting
   directly to the known device. This was the primary cause of the
   'something went wrong' error — the scan would fail with an unrecognized
   error code.

2. LedgerBluetoothAdapter: #doEnsureDeviceReady now throws a named
   DisconnectedDevice error (instead of silently returning false) when the
   transport is null after connect(). The silent false return left the UI
   stuck at 'Connecting' with no feedback.

3. LedgerBluetoothAdapter: #verifyEthereumAppUnlocked now throws a named
   DisconnectedDeviceDuringOperation error (instead of generic
   'Transport not available') when the transport is null. The generic error
   did not match any pattern in parseErrorByType, so it was surfaced as
   ErrorCode.Unknown → 'Something went wrong'. The named error is both
   transient (retried automatically) and maps to DeviceDisconnected if all
   retries are exhausted.

Agent-Logs-Url: https://github.com/MetaMask/metamask-mobile/sessions/fa70675e-fa60-4403-a6eb-2b751536532a

Co-authored-by: dawnseeker8 <7315988+dawnseeker8@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix 'something went wrong' error on Ledger signing fix: Ledger "Something went wrong" error on reconnect during send/sign Apr 2, 2026
Copilot AI requested a review from dawnseeker8 April 2, 2026 07:36
@dawnseeker8 dawnseeker8 marked this pull request as ready for review April 8, 2026 09:01
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@github-actions github-actions Bot added size-M risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 72%
click to see 🤖 AI reasoning details

E2E Test Selection:
The changes are targeted bug fixes for Ledger hardware wallet Bluetooth connection reliability:

  1. LedgerBluetoothAdapter.ts: Changed behavior when no transport is available after connect() - instead of silently returning false, it now throws a named DisconnectedDevice error so the retry loop can treat it as transient and retry. Similarly for the verification step.

  2. useDeviceConnectionFlow.ts: Added pendingTargetDeviceIdRef to store the device ID from the most recent ensureDeviceReady call, allowing retryEnsureDeviceReady to use the correct device ID even when state.deviceId hasn't been populated yet (covers the case where initial BLE open failed before DeviceEvent.Connected was emitted).

These changes are well-contained within the hardware wallet connection layer. The SmokeAccounts tag is selected because it covers hardware wallet account management flows (QR-based hardware wallet accounts, account management). No E2E tests directly test Ledger BLE connectivity as that requires physical hardware, but SmokeAccounts provides the closest coverage for hardware wallet integration paths.

The changes are low-risk in terms of breaking other flows since they only affect the Ledger BLE connection retry logic. The unit tests have been updated to match the new behavior. Confidence is moderate (72) because there are no direct E2E tests for Ledger BLE flows, making it hard to fully validate the fix through automated testing.

Performance Test Selection:
These changes are bug fixes for Ledger Bluetooth connection retry logic. They don't affect UI rendering, data loading, state management, or any performance-sensitive code paths. No performance tests are needed.

View GitHub Actions results

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit aa97022. Configure here.

} else {
// Remember the device ID so retryEnsureDeviceReady can use it even
// when state.deviceId has not been populated yet.
pendingTargetDeviceIdRef.current = targetDeviceId;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale pendingTargetDeviceIdRef not cleared for no-device flows

Medium Severity

When ensureDeviceReady is called without a targetDeviceId (falsy), pendingTargetDeviceIdRef is not cleared. If a previous call stored a device ID in the ref, a subsequent retryEnsureDeviceReady will use the stale value via deviceId ?? pendingTargetDeviceIdRef.current instead of falling through to scanning mode. The if (!targetDeviceId) branch needs to set pendingTargetDeviceIdRef.current = null alongside setDeviceId(null).

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit aa97022. Configure here.

@dawnseeker8
Copy link
Copy Markdown
Contributor

Duplicate PR to #28515

@dawnseeker8 dawnseeker8 closed this Apr 8, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

external-contributor risk-medium Moderate testing recommended · Possible bug introduction risk size-M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: [Ledger] Errors are shown when the ledger is disconnected/ETH app closed/blind signing disabled

3 participants