Summary
JupyterCodeExecutor creates a temp directory via tempfile.mkdtemp() when no output_dir is provided, but never cleans it up in stop(). This is inconsistent with every other executor in the codebase (LocalCommandLineCodeExecutor, DockerCommandLineCodeExecutor, AzureContainerCodeExecutor) which all properly clean up their temp directories. Over time, leaked directories and their contents (generated images, HTML files) accumulate on disk.
Details
When a user creates a JupyterCodeExecutor without specifying output_dir, the constructor creates a temp directory using the low-level tempfile.mkdtemp():
# _jupyter_code_executor.py, line 148
self._output_dir: Path = Path(tempfile.mkdtemp()) if output_dir is None else Path(output_dir)
The class even declares self._temp_dir: Optional[tempfile.TemporaryDirectory[str]] = None on line 151, suggesting the intention was to use tempfile.TemporaryDirectory() (which auto-cleans), but it's never actually used.
When stop() is called, only the kernel context is cleaned up — the output directory is left behind:
# _jupyter_code_executor.py, lines 297-309
async def stop(self) -> None:
if not self._started:
return
if self.kernel_context is not None:
await self.kernel_context.__aexit__(None, None, None)
self.kernel_context = None
self._client = None
self._started = False
# ← No cleanup of self._output_dir
Compare this with how other executors handle it:
LocalCommandLineCodeExecutor (lines 494-503):
async def stop(self) -> None:
if self._temp_dir is not None:
self._temp_dir.cleanup() # ← Properly cleans up
self._temp_dir = None
self._started = False
AzureContainerCodeExecutor (line 520):
self._temp_dir.cleanup() # ← Properly cleans up
DockerCommandLineCodeExecutor (line 443):
self._temp_dir.cleanup() # ← Properly cleans up
Each code execution that produces visual output (matplotlib charts, HTML reports) writes files into the leaked directory. These files persist indefinitely, and over many executor lifecycles, they accumulate.
PoC
import asyncio
from autogen_ext.code_executors.jupyter import JupyterCodeExecutor
from autogen_core.code_executor import CodeBlock
from autogen_core import CancellationToken
async def main():
leaked_dirs = []
for i in range(3):
executor = JupyterCodeExecutor()
leaked_dirs.append(str(executor.output_dir))
await executor.start()
result = await executor.execute_code_blocks(
[CodeBlock(code=f"print('run {i}')", language="python")],
CancellationToken()
)
await executor.stop()
# Verify: all directories still exist after stop()
import os
for d in leaked_dirs:
exists = os.path.exists(d)
print(f"{d} exists after stop(): {exists}")
# Expected: True (BUG - should be cleaned up)
asyncio.run(main())
Output:
/tmp/tmpr1ax38gw exists after stop(): True
/tmp/tmp2i11247w exists after stop(): True
/tmp/tmpvt43odlp exists after stop(): True
Summary
JupyterCodeExecutorcreates a temp directory viatempfile.mkdtemp()when nooutput_diris provided, but never cleans it up instop(). This is inconsistent with every other executor in the codebase (LocalCommandLineCodeExecutor,DockerCommandLineCodeExecutor,AzureContainerCodeExecutor) which all properly clean up their temp directories. Over time, leaked directories and their contents (generated images, HTML files) accumulate on disk.Details
When a user creates a
JupyterCodeExecutorwithout specifyingoutput_dir, the constructor creates a temp directory using the low-leveltempfile.mkdtemp():The class even declares
self._temp_dir: Optional[tempfile.TemporaryDirectory[str]] = Noneon line 151, suggesting the intention was to usetempfile.TemporaryDirectory()(which auto-cleans), but it's never actually used.When
stop()is called, only the kernel context is cleaned up — the output directory is left behind:Compare this with how other executors handle it:
LocalCommandLineCodeExecutor(lines 494-503):AzureContainerCodeExecutor(line 520):DockerCommandLineCodeExecutor(line 443):Each code execution that produces visual output (matplotlib charts, HTML reports) writes files into the leaked directory. These files persist indefinitely, and over many executor lifecycles, they accumulate.
PoC
Output: