langchain-ai/langchain · error · TracerException

Found {run.run_type} run at ID {run_id}, but expected {run_t

Error message

Found {run.run_type} run at ID {run_id}, but expected {run_types} run.

What it means

After resolving a run by id, _get_run verifies the run's type (llm/chain/tool/...) matches the expected type for the event being handled. A mismatch — e.g. treating a chain run as a tool run — means start/end events of different kinds shared one run_id, and raises TracerException.

Source

Thrown at libs/core/langchain_core/tracers/core.py:167

        self.run_map[str(run.id)] = run

    def _get_run(self, run_id: UUID, run_type: str | set[str] | None = None) -> Run:
        try:
            run = self.run_map[str(run_id)]
        except KeyError as exc:
            msg = f"No indexed run ID {run_id}."
            raise TracerException(msg) from exc

        if isinstance(run_type, str):
            run_types: set[str] | None = {run_type}
        else:
            run_types = run_type
        if run_types is not None and run.run_type not in run_types:
            msg = (
                f"Found {run.run_type} run at ID {run_id}, "
                f"but expected {run_types} run."
            )
            raise TracerException(msg)
        return run

    def _create_chat_model_run(
        self,
        serialized: dict[str, Any],
        messages: list[list[BaseMessage]],
        run_id: UUID,
        tags: list[str] | None = None,
        parent_run_id: UUID | None = None,
        metadata: dict[str, Any] | None = None,
        name: str | None = None,
        **kwargs: Any,
    ) -> Run:
        """Create a chat model run."""
        if self._schema_format not in {"streaming_events", "original+chat"}:
            # Please keep this un-implemented for backwards compatibility.
            # When it's unimplemented old tracers that use the "original" format
            # fallback on the on_llm_start method implementation if they

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Use distinct, framework-generated run_ids per run and route end events to the matching on_<type>_end handler
  2. When subclassing BaseTracer, pass the correct run_type to _get_run (or None to skip the check)
  3. Don't reuse a run_id across chain/tool/llm boundaries

Example fix

# before
class MyTracer(BaseTracer):
    def on_tool_end(self, output, *, run_id, **kw):
        run = self._get_run(run_id)               # may find a chain run -> mismatch

# after
class MyTracer(BaseTracer):
    def on_tool_end(self, output, *, run_id, **kw):
        run = self._get_run(run_id, "tool")       # correct type expectation
        # ensure run_id originally came from on_tool_start
Defensive patterns

Strategy: try-catch

Validate before calling

def run_is_type(tracer, run_id, expected: set[str]) -> bool:
    run = tracer.run_map.get(str(run_id))
    return run is not None and run.run_type in expected

Try / catch

from langchain_core.tracers import TracerException

try:
    run = self._get_run(run_id, {"chain", "tool"})
except TracerException:
    return  # event of an unexpected kind; skip

Prevention

When it happens

Trigger: on_tool_end(..., run_id=<id of a chain run>); on_llm_end against a run created by on_chain_start; custom agents emitting reused ids across different run types.

Common situations: Custom callback handlers forwarding all end events to on_chain_end regardless of kind; generating run_ids manually with uuid4() collisions across types; adapters that bridge tracers to other telemetry systems.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/c24db8a5e9f2f7df. Report an issue: GitHub.