Problem
The Textual TUI can crash when a mouse selection event targets a message widget that has just been unmounted during a refresh/remount cycle.
The observed traceback ends in Textual's App.on_event path with an assertion that the mouse-down widget still has a parent. In this session the target was a SelectableStatic message body whose parent was already None.
Classification
This looks like an application/UI race bug, not a model failure, user misuse, or a local-only environment issue:
- The failure happens before the agent/model logic handles the turn.
- Textual reasonably assumes the mouse target is still mounted when dispatching the event.
- GenericAgent's TUI dynamically removes/remounts message widgets while interaction events may still be in flight.
- The local environment only exposes the race (
textual 8.2.7, Python 3.14.5); it is not caused by a user command or invalid configuration.
Root cause
frontends/tuiapp_v2.py renders message bodies as SelectableStatic widgets. During message refresh/layout updates, existing message widgets may be removed and remounted. If a MouseDown/selection event is still queued for one of the removed widgets, Textual's event dispatch path can encounter a detached widget and assert because the target no longer has a parent.
Impact
A normal terminal interaction such as selecting/clicking text while the TUI is refreshing can terminate the whole TUI session.
Proposed fix
Make the TUI resilient to stale selection mouse events by ignoring events whose target widget is no longer attached to the widget tree before Textual reaches the parent assertion path. Add focused regression coverage for detached SelectableStatic/message widgets.
Acceptance criteria
- Detached/stale
SelectableStatic mouse events are ignored instead of crashing.
- Normal selection/click behavior on mounted message widgets continues to work.
- A regression test covers a detached message widget receiving a mouse event.
- Existing TUI tests still pass.
Problem
The Textual TUI can crash when a mouse selection event targets a message widget that has just been unmounted during a refresh/remount cycle.
The observed traceback ends in Textual's
App.on_eventpath with an assertion that the mouse-down widget still has a parent. In this session the target was aSelectableStaticmessage body whoseparentwas alreadyNone.Classification
This looks like an application/UI race bug, not a model failure, user misuse, or a local-only environment issue:
textual 8.2.7, Python3.14.5); it is not caused by a user command or invalid configuration.Root cause
frontends/tuiapp_v2.pyrenders message bodies asSelectableStaticwidgets. During message refresh/layout updates, existing message widgets may be removed and remounted. If aMouseDown/selection event is still queued for one of the removed widgets, Textual's event dispatch path can encounter a detached widget and assert because the target no longer has a parent.Impact
A normal terminal interaction such as selecting/clicking text while the TUI is refreshing can terminate the whole TUI session.
Proposed fix
Make the TUI resilient to stale selection mouse events by ignoring events whose target widget is no longer attached to the widget tree before Textual reaches the parent assertion path. Add focused regression coverage for detached
SelectableStatic/message widgets.Acceptance criteria
SelectableStaticmouse events are ignored instead of crashing.