cocoindex-io/cocoindex · error · RuntimeError

LiveComponentOperator is no longer active. Operator methods

Error message

LiveComponentOperator is no longer active. Operator methods are only valid inside the body of process_live.

What it means

A LiveComponentOperator is detached (its _controller set to None) once the process_live body finishes. Any operator method afterward — update, update_full, delete, mark_ready, state reads/writes — raises RuntimeError because the operator is only valid within the process_live body scope.

Source

Thrown at python/cocoindex/_internal/live_component.py:268

        self._env = env
        self._path = path

    def _detach(self) -> None:
        """Release the Rust controller; subsequent operator calls raise.

        Called by ``_process_live_wrapper`` in a ``finally`` block once
        ``process_live`` returns. After detach, the operator is usable only
        for inspecting its own metadata (``_env``, ``_path``); the
        controller-backed methods (:meth:`update_full`, :meth:`update`,
        :meth:`delete`, :meth:`mark_ready`) raise :class:`RuntimeError`.
        """
        self._controller = None

    def _require_controller(self) -> core.LiveComponentController:
        """Return the controller or raise if already detached."""
        ctrl = self._controller
        if ctrl is None:
            raise RuntimeError(
                "LiveComponentOperator is no longer active. Operator "
                "methods are only valid inside the body of process_live."
            )
        return ctrl

    def _resolve_exception_handler(self) -> Callable[[str], Awaitable[None]]:
        """Build a resolver for the parent's exception handler chain.

        Delegates to :meth:`ComponentContext.resolve_exception_handler`
        — the same path used by ``coco.mount`` / ``coco.mount_each`` —
        so component-failure logs go through one canonical Python
        fallback. Always non-None. Used both by :meth:`update_full`
        (passes to Rust as ``on_error``) and :meth:`report_exception`
        (invokes directly with a stringified exception).
        """
        return get_context_from_ctx().resolve_exception_handler(
            stable_path=self._path.to_string(),
            processor_name=type(self._instance).__name__,

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Perform all operator calls inside the process_live body; await them before returning.
  2. Do not store the operator beyond the body's lifetime; capture needed data instead.
  3. Re-enter process_live to get a fresh operator for new updates.

Example fix

// before
async def body(op):
    tasks.append(asyncio.create_task(do_update(op)))
// after
async def body(op):
    await do_update(op)  # finish within the body
Defensive patterns

Strategy: try-catch

Validate before calling

if operator._controller is None:
    raise RuntimeError("operator detached; re-enter process_live")

Type guard

def is_active(op) -> bool: return getattr(op, '_controller', None) is not None

Try / catch

try:
    await op.update(key, fn, args)
except RuntimeError as e:
    if "no longer active" in str(e):
        schedule_in_next_process_live(fn, args)

Prevention

When it happens

Trigger: Storing the operator passed to process_live in a variable/closure and calling op.update()/op.delete()/etc. after the process_live body returns; using the operator from another task or a later callback.

Common situations: Keeping a reference to the operator on self or in a global for later use; spawning a background task inside process_live that outlives the body and touches the operator.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/078a4f6c3564cb01. Report an issue: GitHub.