Skip to content

Sync APIs corrupt Jupyter kernels on ipykernel >= 7 (nest_asyncio vs anyio-based kernel) #4530

Description

@alexandraabbas

Summary

All sync APIs that route through inspect_ai._util._async.run_coroutine (read_eval_log, list_eval_logs, the inspect_ai.analysis dataframe builders, Inspect Scout's scan(), ...) corrupt the kernel event loop when called from a Jupyter notebook running ipykernel >= 7. In a notebook, run_coroutine applies nest_asyncio2 and calls asyncio.run() on top of the kernel's running loop. ipykernel 7 (released 2025-10-13, now the default install) rewrote message handling as anyio tasks with per-message contexts (Kernel.shell_main, _async_in_context), and nest_asyncio's re-entrant loop running is incompatible with that.

Note the failure is noisy rather than always fatal — cells often still return correct results beneath the error spew — but the nest_asyncio2.apply() patch is process-global, so a single sync call degrades the kernel for the rest of the session. (This may be why it's gone unreported.)

Symptoms

ERROR Exception in callback Task.__step()
RuntimeError: cannot enter context: <_contextvars.Context object at 0x...> is already entered
ERROR Task was destroyed but it is pending!
  task: <Task pending name='Task-24' coro=<Kernel.shell_main() running at .../ipykernel/kernelbase.py:597>
RuntimeWarning: coroutine 'Kernel.shell_main' was never awaited

Errors recur on later cells, including cells that don't touch inspect_ai.

Repro

inspect-ai 0.3.246, nest-asyncio2 1.7.2, Python 3.12, macOS
ipykernel 7.3.0  -> corrupted (above errors)
ipykernel 6.31.0 -> clean (same code)
  1. pip install inspect-ai "ipykernel>=7" jupyterlab
  2. In a notebook cell, call any sync log API, e.g. from inspect_ai.log import list_eval_logs; list_eval_logs("logs/") (any log dir).
  3. Errors appear in that cell's output and in subsequent cells.

Scriptable repro (no live Jupyter needed): execute a two-cell notebook via nbclient — cell 1 calls a sync API, cell 2 is import time; time.sleep(2) — and grep the outputs for Task was destroyed / cannot enter context.

Found while running transcript analysis in JupyterLab.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions