Skip to content

on_tool_error, on_retriever_error, and on_llm_error missing CONTROL_FLOW_EXCEPTION_TYPES check in Langchain callback handler #1515

@vishnumishra

Description

@vishnumishra

Bug

LangGraph uses exceptions inheriting from GraphBubbleUp for control flow — not actual errors:

GraphBubbleUp (base)
├── GraphInterrupt    — raised by interrupt() for human-in-the-loop
│   └── NodeInterrupt — deprecated, same purpose
└── ParentCommand     — raised by Command() for agent handoffs

The Langchain callback handler already defines CONTROL_FLOW_EXCEPTION_TYPES (line 84) and populates it with GraphBubbleUp (line 89) to filter these out. on_chain_error uses this check correctly (line 583), but three other error handlers do not:

Handler Line Has check?
on_chain_error 572 Yes
on_tool_error 796 No
on_retriever_error 245 No
on_llm_error 990 No

The most impactful is on_tool_error, since LangGraph tools are the primary place where interrupt() and Command() are called.

Impact

Any LangGraph tool that uses interrupt() (human-in-the-loop) or returns a Command() (agent handoff) is incorrectly marked as a red ERROR in Langfuse traces. This pollutes dashboards and makes it impossible to distinguish real tool failures from normal control flow.

Expected behavior

Control-flow exceptions should be recorded with level="DEFAULT" and preserve the status message so users can see why the run stopped, without being flagged as an error.

Suggested fix

Apply the same CONTROL_FLOW_EXCEPTION_TYPES guard to all three handlers, using level="DEFAULT" (instead of None as on_chain_error currently does) to preserve visibility:

if any(isinstance(error, t) for t in CONTROL_FLOW_EXCEPTION_TYPES):
    level = "DEFAULT"
else:
    level = "ERROR"

observation.update(
    level=cast(Optional[Literal["DEBUG", "DEFAULT", "WARNING", "ERROR"]], level),
    status_message=str(error),
    ...
)

The same improvement should also be applied to on_chain_error for consistency — its current level=None / status_message=None behavior makes control-flow interruptions invisible in traces.

Related issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions