Description
In local_browser.py, _attach_page_listeners() (line 370-372) registers page.on('console', ...) and page.on('pageerror', ...) handlers, but these are never removed in _close_async() (line 533).
The _close_async() method nullifies the self._page reference (line 541) but never calls page.remove_listener(). The Playwright pyee event emitter retains references to the bound methods, creating a memory leak.
Impact
- Over long agent runs, listener references accumulate
- Each listener closure captures the bound method, preventing GC
- Memory grows linearly with agent steps
- This is a known pattern - the Playwright Python repo has multiple confirmed reports of memory leaks from orphaned
page.on() handlers
Suggested Fix
Store the listener removal in a cleanup list and call _detach_page_listeners() at the start of _close_async():
def _attach_page_listeners(self, page):
def on_console(msg): self._on_console_message(msg)
def on_page_error(err): self._on_page_error(err)
page.on("console", on_console)
page.on("pageerror", on_page_error)
self._page_cleanup = [
lambda: page.remove_listener("console", on_console),
lambda: page.remove_listener("pageerror", on_page_error),
]
def _detach_page_listeners(self):
for cleanup in self._page_cleanup:
try:
cleanup()
except Exception:
pass
self._page_cleanup = []
Description
In
local_browser.py,_attach_page_listeners()(line 370-372) registerspage.on('console', ...)andpage.on('pageerror', ...)handlers, but these are never removed in_close_async()(line 533).The
_close_async()method nullifies theself._pagereference (line 541) but never callspage.remove_listener(). The Playwright pyee event emitter retains references to the bound methods, creating a memory leak.Impact
page.on()handlersSuggested Fix
Store the listener removal in a cleanup list and call
_detach_page_listeners()at the start of_close_async():