Skip to content

"Resorting to unclean kill browser." when using fig.write_image() #430

Description

@ocapese

Using:
MacOS
uv for package management
Python 3.13.1
Kaleido 1.2.0
nbformat 5.10.4
choreographer 1.2.1
plotly 6.5.2

I have recently moved to a new computer (previously I was using Windows11, Plotly 6.0.0 and Kaleido 0.2.1) and now writing images is taking forever and throwing the "Resorting to unclean kill browser." message

For instance, this simple script:

import plotly.graph_objects as go
fig = go.Figure()
fig.write_image('aaa.png')

Output: Resorting to unclean kill browser.

is taking around 9 seconds.

The images are still being produced and saved correctly but the times are too long. Is there a fix?

Thank you

Activity

  1. ocapese commented on Feb 20, 2026

    @ocapese
    Author

    In another instance the following error message was displayed, the code was the same but I was not able to have it appear again:

    ERROR:tornado.general:Uncaught exception in ZMQStream callback
    Traceback (most recent call last):
    File "~/.venv/lib/python3.13/site-packages/traitlets/traitlets.py", line 632, in get
    value = obj._trait_values[self.name]
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^
    KeyError: '_control_lock'

    During handling of the above exception, another exception occurred:

    File "~/.venv/lib/python3.13/site-packages/traitlets/traitlets.py", line 831, in error
    raise TraitError(e)
    traitlets.traitlets.TraitError: The '_control_lock' trait of an IPythonKernel instance expected a Lock, not the NoneType None.
    Resorting to unclean kill browser.

  2. charity-g commented on May 5, 2026

    @charity-g

    The exact same behavior is occurring for me:

    Using:
    Windows
    pip for package management
    Python Python 3.11.5
    Kaleido 1.3.0
    nbformat not installed
    choreographer 1.3.0
    plotly 6.6.0

    even if you manually pin chrome version

    import plotly.graph_objects as go
    import plotly.io as pio
    import os
    
    chrome_path  = pio.get_chrome() 
    print(chrome_path)
    
    os.environ["BROWSER_PATH"] = str(chrome_path)
    
    import plotly.graph_objects as go
    
    fig = go.Figure(go.Scatter(x=[1, 2, 3], y=[1, 3, 2]))
    fig.write_image("test.png")
    
  3. camdecoster commented on May 6, 2026

    @camdecoster
    Contributor

    Thanks for the report. We're going to try to reproduce the issue locally and follow up.

  4. self-assigned this
    on May 6, 2026
  5. camdecoster commented on May 7, 2026

    @camdecoster
    Contributor

    I tried running both examples on Ubuntu Linux and didn't see any issue. What version of Chrome do you have installed?

  6. berkdemir commented on May 7, 2026

    @berkdemir

    I have the same issue. See the report below.

    Confirming this issue on Kaleido 1.2.0 — significantly worse than 9 s per call

    Environment:

    OS: Windows 11 (10.0.26200)
    Python: 3.12.8
    Plotly: 6.5.2
    Kaleido: 1.2.0 (previously: Kaleido 0.2.1 on a different machine, Plotly 6.0.0 — no issues)
    Observed behaviour:

    Same "Resorting to unclean kill browser." message on every call. In my case each pio.to_image() takes approximately 14–15 seconds, which is worse than the 9 s reported above. Images are produced correctly, so this is purely a performance regression.

    The severity becomes clear in any script that exports multiple figures. I have a report pipeline that calls pio.to_image() six times (three beam diagrams × two load cases). Profiling shows:

    Step Duration
    All structural beam analysis (SymPy + Plotly figure creation) 283 ms
    PDF assembly — 6 × pio.to_image() via Kaleido 86,900 ms (~87 s)
    The entire computation is effectively held hostage by Kaleido. The browser appears to be killed and restarted on every single call rather than staying warm between exports. On Kaleido 0.2.1 the same script completed in a few seconds total.

    Workaround found: None. Downgrading to Kaleido 0.2.1 is not straightforward because its API is incompatible with Plotly 6.x.

    Update — same issue on Kaleido 1.3.0

    Upgraded from 1.2.0 → 1.3.0 (choreographer 1.3.0). No change in behaviour.

    Environment:

    OS: Windows 11 (10.0.26200)
    Python: 3.12.8
    Plotly: 6.5.2
    Kaleido: 1.3.0 (choreographer 1.3.0)
    Detailed profiling of a 6-figure report:

    Duration Step
    < 3 ms All non-Kaleido steps (cover, chapters, tables, ReportLab assembly)
    10,031 ms fig_beam demoulding — pio.to_image() [KALEIDO]
    9,426 ms fig_shear demoulding — pio.to_image() [KALEIDO]
    10,133 ms fig_moment demoulding — pio.to_image() [KALEIDO]
    9,543 ms fig_beam stacking — pio.to_image() [KALEIDO]
    11,998 ms fig_shear stacking — pio.to_image() [KALEIDO]
    9,870 ms fig_moment stacking — pio.to_image() [KALEIDO]
    406 ms r.build() — ReportLab PDF assembly
    ~61 seconds TOTAL — 99.3% spent in Kaleido
    Every single call takes ~10 seconds. The browser is clearly not being kept warm between exports. Non-Kaleido work (SymPy beam analysis, Plotly figure creation, ReportLab PDF layout) totals under 700 ms for the entire pipeline. The problem is entirely isolated to pio.to_image().

  7. camdecoster commented on May 8, 2026

    @camdecoster
    Contributor

    What version of Chrome are you using?

  8. charity-g commented on May 8, 2026

    @charity-g

    I tried running both examples on Ubuntu Linux and didn't see any issue. What version of Chrome do you have installed?

    Chrome version, installed on Windows:

    Google Chrome | 146.0.7680.216 (Official Build) extended (64-bit) (cohort: 148.0.7778.56 Rollout)
    -- | --
    Revision | ff9214069a282850ff7ebbef6615bb009cf45c57-refs/branch-heads/7680@{#4013}
    OS | Windows 11 Version 24H2 (Build 26100.8246)
    JavaScript | V8 14.6.202.34
    User Agent | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36
    Command Line | "C:\Program Files\Google\Chrome\Application\chrome.exe" --flag-switches-begin --flag-switches-end
    Executable Path | C:\Program Files\Google\Chrome\Application\chrome.exe
    

    Even if I attempt to manually pin the google chrome location

    import os
    os.environ["BROWSER_PATH"] = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
    
    import plotly.graph_objects as go
    
    fig = go.Figure(go.Scatter(x=[1, 2, 3], y=[1, 3, 2]))
    fig.write_image("hello.png", width=200, height=200)

    I will still get the same runtime error

    Resorting to unclean kill browser.
    Traceback (most recent call last):
      File "C:\Users\username_redacted\Downloads\Code\dash-apps\test.py", line 7, in <module>
        fig.write_image("hello.png", width=200, height=200)
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\plotly\basedatatypes.py", line 3895, in write_image
        return pio.write_image(self, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\plotly\io\_kaleido.py", line 528, in write_image
        img_data = to_image(
                   ^^^^^^^^^
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\plotly\io\_kaleido.py", line 398, in to_image
        img_bytes = kaleido.calc_fig_sync(
                    ^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\kaleido\__init__.py", line 170, in calc_fig_sync
        return _sync_server.oneshot_async_run(calc_fig, args=args, kwargs=kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\kaleido\_sync_server.py", line 161, in oneshot_async_run
        raise res
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\kaleido\_sync_server.py", line 152, in run
        q.put(asyncio.run(func(*args, **kwargs)))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Program Files\Python311\Lib\asyncio\runners.py", line 190, in run
        return runner.run(main)
               ^^^^^^^^^^^^^^^^
      File "C:\Program Files\Python311\Lib\asyncio\runners.py", line 118, in run
        return self._loop.run_until_complete(task)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Program Files\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete
        return future.result()
               ^^^^^^^^^^^^^^^
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\kaleido\__init__.py", line 103, in calc_fig
        async with Kaleido(**kopts) as k:
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\kaleido\kaleido.py", line 297, in __aexit__
        return await super().__aexit__(exc_type, exc_value, exc_tb)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\choreographer\browser_async.py", line 294, in __aexit__
        await self.close()
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\kaleido\kaleido.py", line 273, in close
        await super().close()
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\choreographer\browser_async.py", line 270, in close
        await self._close()
      File "C:\Users\username_redacted\AppData\Roaming\Python\Python311\site-packages\choreographer\browser_async.py", line 256, in _close
        raise RuntimeError("Couldn't close or kill browser subprocess")
    RuntimeError: Couldn't close or kill browser subprocess
    
  9. ocapese commented on May 27, 2026

    @ocapese
    Author

    Hello, I'm the author of this thread.

    In the end I managed to fix this by
    Python 3.13.1
    Kaleido 0.2.1
    choreographer 1.2.1
    nbformat 5.10.4
    plotly 6.0.0

    It has been a while, I'm not sure which step worked but I haven't had any issue since.

  10. camdecoster commented on Jul 2, 2026

    @camdecoster
    Contributor

    I'm still working on investigating the slow time to shutdown Chrome, but here's an undocumented workaround for modern Kaleido: use kaleido.start_sync_server() before calling write_image.

    Some background: calls to write_image start up Chrome, render the image, then shut Chrome down. That can lead to long times to render if you're calling it multiple times. kaleido.start_sync_server() starts up Chrome and keeps it running until the script completes. So, multiple calls to write_image use the same Chrome instance and you only have to wait for startup/shutdown once.

    Here's an example:

    import kaleido
    import plotly.graph_objects as go
    
    kaleido.start_sync_server()   # one-time; Chrome stays warm
    
    fig1 = go.Figure(go.Scatter(x=[1, 2, 3], y=[1, 3, 2]))
    fig1.write_image("a.png")     # first call: cold start
    fig2 = go.Figure(go.Scatter(x=[1, 2, 3], y=[2, 1, 3]))
    fig2.write_image("b.png")     # reuses warm Chrome

    I'll get our README updated to mention this. @charity-g I'm going to add some better logging that might help us track down what's happening in your case. I'll ping you when that's available.

    EDIT: README updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions