[BUG] `Live`s don't get refreshed after first run in Jupyter notebooks

#4207 · open · 1 comments

View on GitHub ↗

santisoler

- [x] I've checked [docs](https://rich.readthedocs.io/en/latest/introduction.html) and [closed issues](https://github.com/Textualize/rich/issues?q=is%3Aissue+is%3Aclosed) for possible solutions. - [x] I can't find my issue in the [FAQ](https://github.com/Textualize/rich/blob/master/FAQ.md). <!-- WAIT! Is this a feature request? Rich isn't accepting new features. If it can be implemented in a third-party library, then that is what I recommend. Rich *may* accept enhancements to existing features, if they don't have a disruptive impact on the API. If you think your feature is exceptional, then please make your case in a discussion. --> **Describe the bug** I noticed that in Jupyter notebooks, any `Live` environment that is run after a first one has been executed before doesn't get automatic refreshes. I don't see this happening in the terminal, and I cannot see any hint in the documentation or Issues on how to fix this. For example, let's consider this code: ```python import time from rich.text import Text from rich.live import Live from rich.progress import Spinner from rich.console import Group # Define a spinner and a text, and group them together spinner = Spinner(name="dots", text="Running...", style="green", speed=1) text = Text("Blah") group = Group(text, spinner) # Run a Live environment and append text after each iteration with Live(group) as live: for _ in range(5): time.sleep(1) text.append_text(Text(" blah")) # Run the same task again (with a new Live) with Live(group) as live: for _ in range(5): time.sleep(1) text.append_text(Text(" blah")) ``` If I run this in the terminal, I see both `Live` environments working as expected: the text gets new appended words after each iteration, and the spinner works great in both cases: ``` Blah blah blah blah blah blah ⠹ Running... Blah blah blah blah blah blah blah blah blah blah blah ⠴ Running... ``` But when trying to run the same in a notebook, the second run doesn't automatically refresh: it shows just the initial state of the `Group` for 5 seconds, and then the cell finishes its execution, without any update in the output. <img width="1273" height="686" alt="Image" src="https://github.com/user-attachments/assets/5cca20e4-7694-43c2-befb-6527e3d4d794" /> I'm using JupyterLab here, but I also notice the same behaviour in Jupyter Notebooks. **A few comments** I know that I could run `live.refresh()` after each iteration, and that will partially solve the issue. But, I intentionally added a spinner here, because such trick will not force the spinner to refresh in higher rate. I'm suspecting this issue might be coming from the underlying ipywidget that `Live` creates, but I'm not sure if that's the case, and I'm not so familiar with ipywidgets to debug it yet. I see the same behaviour even when using `transient=True`. I could provide a minimal example if required. Lastly, thanks a lot for developing Rich! **Platform** <details> <summary>Click to expand</summary> What platform (Win/Linux/Mac) are you running on? What terminal software are you using? Linux. I'm using Gnome Terminal and JupyterLab. I may ask you to copy and paste the output of the following commands. It may save some time if you do it now. ```python from rich.diagnose import report report() ``` ``` ╭────────────────────── <class 'rich.console.Console'> ──────────────────────╮ │ A high level console interface. │ │ │ │ ╭────────────────────────────────────────────────────────────────────────╮ │ │ │ <console width=115 ColorSystem.TRUECOLOR> │ │ │ ╰────────────────────────────────────────────────────────────────────────╯ │ │ │ │ color_system = 'truecolor' │ │ encoding = 'utf-8' │ │ file = <ipykernel.iostream.OutStream object at 0x7f2ea866d180> │ │ height = 100 │ │ is_alt_screen = False │ │ is_dumb_terminal = False │ │ is_interactive = False │ │ is_jupyter = True │ │ is_terminal = False │ │ legacy_windows = False │ │ no_color = False │ │ options = ConsoleOptions( │ │ size=ConsoleDimensions(width=115, height=100), │ │ legacy_windows=False, │ │ min_width=1, │ │ max_width=115, │ │ is_terminal=False, │ │ encoding='utf-8', │ │ max_height=100, │ │ justify=None, │ │ overflow=None, │ │ no_wrap=False, │ │ highlight=None, │ │ markup=None, │ │ height=None │ │ ) │ │ quiet = False │ │ record = False │ │ safe_box = True │ │ size = ConsoleDimensions(width=115, height=100) │ │ soft_wrap = False │ │ stderr = False │ │ style = None │ │ tab_size = 8 │ │ width = 115 │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭─── <class 'rich._windows.WindowsConsoleFeatures'> ────╮ │ Windows features available. │ │ │ │ ╭───────────────────────────────────────────────────╮ │ │ │ WindowsConsoleFeatures(vt=False, truecolor=False) │ │ │ ╰───────────────────────────────────────────────────╯ │ │ │ │ truecolor = False │ │ vt = False │ ╰───────────────────────────────────────────────────────╯ ╭────── Environment Variables ───────╮ │ { │ │ 'CLICOLOR': '1', │ │ 'COLORTERM': 'truecolor', │ │ 'COLUMNS': None, │ │ 'JPY_PARENT_PID': '26737', │ │ 'JUPYTER_COLUMNS': None, │ │ 'JUPYTER_LINES': None, │ │ 'LINES': None, │ │ 'NO_COLOR': None, │ │ 'TERM_PROGRAM': 'tmux', │ │ 'TERM': 'xterm-color', │ │ 'TTY_COMPATIBLE': None, │ │ 'TTY_INTERACTIVE': None, │ │ 'VSCODE_VERBOSE_LOGGING': None │ │ } │ ╰────────────────────────────────────╯ platform="Linux" ``` </details>

Comments

mliotta

AI-generated investigation and proposed fix by OpenAI Codex (`gpt-6-astra`). I reproduced this on Rich commit `9d8f9a372cc5916fd4781fec207ced7ddac2f08f` in a real JupyterLab frontend: the first `Live` updates, while the second stays at its initial text and spinner state after the cell finishes. A separate real-kernel capture still emitted later `display_data` frames for both runs, so testing frame emission alone misses the visible failure. The proposed change is confined to the Jupyter branch of `Live.refresh()`: render the current frame to Rich's existing plain-text and HTML representations, then replace that specific `Output` widget's synchronized `outputs` tuple. This avoids per-frame `with Output(): clear_output(); display(...)` capture and its shared parent-message routing. Console styling and recording are retained; terminal and nested-Live control paths are unchanged. Applying the patch to a clean source copy makes both consecutive displays advance in the actual frontend. The existing `test_live.py`, `test_console.py`, and `test_jupyter.py` suites pass (114 tests). This is a local frontend check, not a claim of coverage across all notebook/widget versions. Would you approve this approach for a PR with a regression test and the reproduction notebook? I have not opened a PR, in accordance with AI_POLICY.md.