fix(core/eckhart): don't close device menu on `GetFeatures`
What changed, and why it matters
This is a user-experience bug fix for the Trezor T3W1 hardware wallet. Previously, asking the device for its basic info (a GetFeatures request) would restart the device's workflow and close any open menu. The fix makes the device ignore that restart behavior for GetFeatures on the T3W1 model, so settings menus stay open. It is not a security vulnerability fix.
No security action required; treat as normal UX fix. Reviewers may verify the ECKHART-only gating and heap rationale are correct, and that no other message types were inadvertently added to AVOID_RESTARTING_FOR.
Security signals we found
No security-relevant signals in diff
Change is a UX/workflow behavior fix, not a memory safety, authentication, or cryptographic fix
No input parsing, serialization, or privilege boundary changes
Evidence from the diff
The commit adds MessageType.GetFeatures to the AVOID_RESTARTING_FOR tuple only when utils.UI_LAYOUT == “ECKHART” (T3W1/TS7). In handle_single_message, returning True for types in AVOID_RESTARTING_FOR prevents the MicroPython event loop/workflow from being restarted. The change is gated to the ECKHART layout because that model has the largest heap. A regression test verifies the device menu remains open after session.refresh_features().
Changed components
core/src/trezor/wire/message_handler.pyTrezor T3W1 (ECKHART UI layout) device workflow handlingInspect captured patch +27 / −2
diff --git a/core/.changelog.d/6211.fixed b/core/.changelog.d/6211.fixed
new file mode 100644
index 00000000..3ae78f34
--- /dev/null
+++ b/core/.changelog.d/6211.fixed
@@ -0,0 +1 @@
+[T3W1] Fix menu closing after change of settings.
diff --git a/core/src/trezor/wire/message_handler.py b/core/src/trezor/wire/message_handler.py
index 82ae3f13..f97590d8 100644
--- a/core/src/trezor/wire/message_handler.py
+++ b/core/src/trezor/wire/message_handler.py
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING
from storage.cache_common import InvalidSessionError
from trezor import log, loop, protobuf, utils, workflow
-from trezor.enums import FailureType
+from trezor.enums import FailureType, MessageType
from trezor.messages import Failure
from .context import UnexpectedMessageException, with_context
@@ -179,7 +179,11 @@ async def handle_single_message(ctx: Context, msg: Message) -> bool:
return msg.type in AVOID_RESTARTING_FOR
-AVOID_RESTARTING_FOR: Container[int] = ()
+if utils.UI_LAYOUT == "ECKHART":
+ # Don't close device menu when `GetFeatures` is received.
+ AVOID_RESTARTING_FOR: Container[int] = (MessageType.GetFeatures,)
+else:
+ AVOID_RESTARTING_FOR: Container[int] = ()
def failure(exc: BaseException) -> Failure:
diff --git a/tests/device_tests/test_basic.py b/tests/device_tests/test_basic.py
index a53bd715..80d9f2dc 100644
--- a/tests/device_tests/test_basic.py
+++ b/tests/device_tests/test_basic.py
@@ -21,6 +21,8 @@ from trezorlib.client import get_client
from trezorlib.debuglink import DebugSession as Session
from trezorlib.debuglink import TrezorTestContext as Client
+from ..click_tests.device_menu.common import open_device_menu
+
def test_capabilities(session: Session):
assert (messages.Capability.Translations in session.features.capabilities) == (
@@ -87,3 +89,15 @@ def test_desync_v1(client: Client):
# Creating a new client fails without skipping stale responses
# (see https://github.com/trezor/trezor-firmware/issues/6859)
get_client(client.app, client.transport).ping("reconnect")
+
+
+@pytest.mark.models("eckhart")
+def test_get_features_avoids_restart(session: Session):
+ debug = session.debug
+ assert "Homescreen" == debug.read_layout().main_component()
+ open_device_menu(debug)
+ assert "DeviceMenuScreen" == debug.read_layout().main_component()
+
+ # GetFeatures doesn't restart MicroPython event loop - device menu is still open.
+ session.refresh_features()
+ assert "DeviceMenuScreen" == debug.read_layout().main_component()
diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json
index 3bfd83ab..854634bb 100644
--- a/tests/ui_tests/fixtures.json
+++ b/tests/ui_tests/fixtures.json
@@ -36261,6 +36261,7 @@
"T3W1_cs_test_basic.py::test_capabilities": "25675032fe6b1628161f0cd2398edae2feace6873ae31c44ce545c91f7ced796",
"T3W1_cs_test_basic.py::test_device_id_different": "fd25ae13e27b12b1a143dccc454c9beb254327b60c48d5e505223fa0fed56a2c",
"T3W1_cs_test_basic.py::test_device_id_same": "25675032fe6b1628161f0cd2398edae2feace6873ae31c44ce545c91f7ced796",
+"T3W1_cs_test_basic.py::test_get_features_avoids_restart": "3dad4c30ec8a669c6e31de57bc811f41ae46b8e03e88c6acb00e27083562228c",
"T3W1_cs_test_basic.py::test_not_initialized": "bb5baca9a34168732bf0521a0546df8fc6af1af0145fff6e9d6357f2687a9c35",
"T3W1_cs_test_basic.py::test_ping": "25675032fe6b1628161f0cd2398edae2feace6873ae31c44ce545c91f7ced796",
"T3W1_cs_test_ble.py::test_ble_unpair_all": "b5192bbcbafa193da170181ff2b97ab70a004eb263dc033b5d34774ec028c26d",
@@ -38061,6 +38062,7 @@
"T3W1_de_test_basic.py::test_capabilities": "aaafdc361a1d1494db1b142427ede204b978fbde090811b1ff02e54df49414f9",
"T3W1_de_test_basic.py::test_device_id_different": "59f7a6080bade0af4b5269af9dfe0062c5fbfc2129563ee12cc7825277bbf527",
"T3W1_de_test_basic.py::test_device_id_same": "aaafdc361a1d1494db1b142427ede204b978fbde090811b1ff02e54df49414f9",
+"T3W1_de_test_basic.py::test_get_features_avoids_restart": "a07b683a019d3f4d5a57d8636c2e6b945dad42c37a4e3fd0923c37a3de55f0b9",
"T3W1_de_test_basic.py::test_not_initialized": "bb5baca9a34168732bf0521a0546df8fc6af1af0145fff6e9d6357f2687a9c35",
"T3W1_de_test_basic.py::test_ping": "aaafdc361a1d1494db1b142427ede204b978fbde090811b1ff02e54df49414f9",
"T3W1_de_test_ble.py::test_ble_unpair_all": "ba03b09d14fcfdbeaaeb75c118b7203f5e0395bb08ecfad6c65b567358f36eed",
@@ -39861,6 +39863,7 @@
"T3W1_en_test_basic.py::test_capabilities": "778dbcfb96e575c652b06902ef2842f6cc261dd3d457cc66b8b6c9338e82e1fb",
"T3W1_en_test_basic.py::test_device_id_different": "67ad9cb76379e06b5469507fff542e4fe7d1dbc8307208c11850849ef375ca22",
"T3W1_en_test_basic.py::test_device_id_same": "778dbcfb96e575c652b06902ef2842f6cc261dd3d457cc66b8b6c9338e82e1fb",
+"T3W1_en_test_basic.py::test_get_features_avoids_restart": "c42a444dd3ebacde8eabf571685c6225b4df3a5a0fe5fb797da6b7c82915e385",
"T3W1_en_test_basic.py::test_not_initialized": "bb5baca9a34168732bf0521a0546df8fc6af1af0145fff6e9d6357f2687a9c35",
"T3W1_en_test_basic.py::test_ping": "778dbcfb96e575c652b06902ef2842f6cc261dd3d457cc66b8b6c9338e82e1fb",
"T3W1_en_test_ble.py::test_ble_unpair_all": "71a1991a5a1922d57b394dcb469b15398a9f6f9bd04316c6dc10ae4055c65469",
@@ -41661,6 +41664,7 @@
"T3W1_es_test_basic.py::test_capabilities": "ea59c9d24216e9bad1b1fdd99068e34dbc5beeea54b06c34219d37720f17a470",
"T3W1_es_test_basic.py::test_device_id_different": "869f9899cdc444edc27470462341a174ff4ab11a989b58c9b1077606cd4f188f",
"T3W1_es_test_basic.py::test_device_id_same": "ea59c9d24216e9bad1b1fdd99068e34dbc5beeea54b06c34219d37720f17a470",
+"T3W1_es_test_basic.py::test_get_features_avoids_restart": "695dc3db5298866024e4cc18c326b586707ef511a39e447307713159b20a419e",
"T3W1_es_test_basic.py::test_not_initialized": "bb5baca9a34168732bf0521a0546df8fc6af1af0145fff6e9d6357f2687a9c35",
"T3W1_es_test_basic.py::test_ping": "ea59c9d24216e9bad1b1fdd99068e34dbc5beeea54b06c34219d37720f17a470",
"T3W1_es_test_ble.py::test_ble_unpair_all": "98411a2b1f4f07dec02a67b4b42c8d7b6e7778761591e3afb0abc92b4550a2a0",
@@ -43461,6 +43465,7 @@
"T3W1_fr_test_basic.py::test_capabilities": "1cceb37657549d7798a606fb01cffd95cdd3ab59cda2e25228d11f1bb906cd31",
"T3W1_fr_test_basic.py::test_device_id_different": "8f68e6a7214e9431fa936f90ee34f7c69c8ffc54e36b29098c5c801167e3baaf",
"T3W1_fr_test_basic.py::test_device_id_same": "1cceb37657549d7798a606fb01cffd95cdd3ab59cda2e25228d11f1bb906cd31",
+"T3W1_fr_test_basic.py::test_get_features_avoids_restart": "1e27b7d838810ee2b498f360f6e7a95c5cca858428adf7321c4f34dcd317e7c3",
"T3W1_fr_test_basic.py::test_not_initialized": "bb5baca9a34168732bf0521a0546df8fc6af1af0145fff6e9d6357f2687a9c35",
"T3W1_fr_test_basic.py::test_ping": "1cceb37657549d7798a606fb01cffd95cdd3ab59cda2e25228d11f1bb906cd31",
"T3W1_fr_test_ble.py::test_ble_unpair_all": "88a97e9db80f32e91ab227b784331197ad1cffa680673a065cf888855c9513fc",
@@ -45266,6 +45271,7 @@
"T3W1_pt_test_basic.py::test_capabilities": "67695a9de4ea2860aa20568a42ba0dd34e1fdf983ff4cf32347e6dd81e79c6e3",
"T3W1_pt_test_basic.py::test_device_id_different": "59afbbc2ae55e9e39e143bba1b5861748402bd43c65923931da927a20e0bf014",
"T3W1_pt_test_basic.py::test_device_id_same": "67695a9de4ea2860aa20568a42ba0dd34e1fdf983ff4cf32347e6dd81e79c6e3",
+"T3W1_pt_test_basic.py::test_get_features_avoids_restart": "ac6a2acd339fbd801b33fcf96d8d98e295253c799f20383e28f4cf2d1fa9a299",
"T3W1_pt_test_basic.py::test_not_initialized": "bb5baca9a34168732bf0521a0546df8fc6af1af0145fff6e9d6357f2687a9c35",
"T3W1_pt_test_basic.py::test_ping": "67695a9de4ea2860aa20568a42ba0dd34e1fdf983ff4cf32347e6dd81e79c6e3",
"T3W1_pt_test_ble.py::test_ble_unpair_all": "669a44ed07c49de4b57d70159d3389ba31faa83870db90f1fbcdeb3d997ee522",
Why this scored 19/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.