-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Open
Description
Summary
I've encountered a reproducible RecursionError: maximum recursion depth exceeded when pushing a new Screen on top of another translucent Screen that both contain a Header(show_clock=True).
This issue occurs in a minimal example and does not appear to be caused by application logic. Small changes such as disabling the clock or removing screen translucency eliminate the crash (according to my testing).
FYI: This bug does occur in the latest version (textual==7.4.0), but this bug was first discovered while using textual==7.3.0.
Let me know if you'd like me to further reduce this example or test a proposed fix. Happy to help debug further.
Minimal Reproducible Example
from textual.app import App, ComposeResult
from textual.widgets import Button, Label, Footer, Header, DirectoryTree
from textual.screen import Screen
# How the app works:
# A simple screen that allows you to select a file in the current directory.
# Press 'n' to open the current directory and choose a file. Press ESC to return to main screen.
# When you click or press enter on a file, a popup appears asking you to confirm your choice.
# Select 'Cancel' to go back to the directory tree and choose another choice or cancel.
# Select 'Confirm' to go back to the main screen and see "File Selected: {file}"
# How to reproduce the bug:
# 1. Open the app
# 2. Press 'n' to open file directory
# 3. Click a file to select it (or press enter)
# 4. A screen will appear asking you to confirm your choice <- Where the app crashes
# 5. Confirm your choice, and you will return to the main screen
# If the app doesn't crash on the first try, try loading another file or restarting.
# Eventually, when prompted to confirm, the program should crash with the following message:
# RecursionError: maximum recursion depth exceeded
# Applying ANY of the following changes appears to FIX the bug (what I could find):
# - Initializing either of 'ChooseFile' or 'MainScreen' Header widget with show_clock = False
# - Changing the 'Screen' CSS opacity to 100%
class ConfirmScreen(Screen[bool]):
"""Screen with a dialog to confirm or cancel."""
def compose(self) -> ComposeResult:
yield Label("Are you sure?")
yield Button("Confirm", variant="error")
yield Button("Cancel", variant="primary")
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Dismisses screen with choice (confirm or cancel)."""
self.dismiss(event.button.variant == "error")
class ChooseFile(Screen[str]):
"""A screen with a directory tree. User chooses a file and confirms choice."""
def compose(self) -> ComposeResult:
yield Header(show_clock = True) # Change show_clock = False to fix bug!
yield DirectoryTree("./")
yield Footer()
def on_mount(self) -> None:
self.title = "Select File to Load"
self.query_one(DirectoryTree).focus()
def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected) -> None:
"""When a file is selected, confirm with the user then dismiss with file's path"""
def _confirm(confirmed: bool | None) -> None:
if confirmed is not None and confirmed:
self.dismiss(str(event.path))
self.app.push_screen(ConfirmScreen(), _confirm)
BINDINGS = [("escape", "cancel", "Cancel")]
def action_cancel(self) -> None:
"""Cancel the request to choose a new file."""
self.dismiss(None)
class MainScreen(Screen):
def compose(self) -> ComposeResult:
yield Header(show_clock = True) # Change show_clock = False to fix bug!
yield Label("No file selected (Press n)")
yield Footer()
BINDINGS = [("n", "load_new_file", "Load New File")]
def action_load_new_file(self):
"""Prompt user to choose a new file, and print choice to the label."""
def _choose_file(file: str | None) -> None:
if file is not None:
self.query_one(Label).content = f"File Selected: {file}"
self.app.push_screen(screen = ChooseFile(), callback = _choose_file)
class MRE(App[None]):
CSS = """
Screen {
background: $background 50%; # Change opacity to 100% to fix bug!
}
"""
def on_mount(self) -> None:
self.push_screen(MainScreen())
if __name__ == "__main__":
MRE().run()Screenshot
Textual Diagnostics
Versions
| Name | Value |
|---|---|
| Textual | 7.4.0 |
| Rich | 14.2.0 |
Python
| Name | Value |
|---|---|
| Version | 3.13.11 |
| Implementation | CPython |
| Compiler | Clang 17.0.0 (clang-1700.4.4.1) |
| Executable | /Users/kareemalzahal/Desktop/AIN/AlfalfaOptim/.venv/bin/python3.13 |
Operating System
| Name | Value |
|---|---|
| System | Darwin |
| Release | 25.2.0 |
| Version | Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:55 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T8103 |
Terminal
| Name | Value |
|---|---|
| Terminal Application | Apple_Terminal (466) |
| TERM | xterm-256color |
| COLORTERM | truecolor |
| FORCE_COLOR | Not set |
| NO_COLOR | Not set |
Rich Console options
| Name | Value |
|---|---|
| size | width=95, height=40 |
| legacy_windows | False |
| min_width | 1 |
| max_width | 95 |
| is_terminal | True |
| encoding | utf-8 |
| max_height | 40 |
| justify | None |
| overflow | None |
| no_wrap | False |
| highlight | None |
| markup | None |
| height | None |
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels