langchain-ai/langchain · error · ValueError

RunnableBranch branches must be tuples or lists of length 2,

Error message

RunnableBranch branches must be tuples or lists of length 2, not {len(branch)}

What it means

Each `(condition, runnable)` pair passed to `RunnableBranch.__init__` must have exactly 2 elements (`_MIN_BRANCHES == 2`). A tuple or list of any other length (1, 3, ...) fails the `len(branch) != _MIN_BRANCHES` check and raises `ValueError` with the actual length.

Source

Thrown at libs/core/langchain_core/runnables/branch.py:127

        default_ = coerce_to_runnable(cast("Runnable[Input, Output]", default))

        branches_ = []

        for branch in branches[:-1]:
            if not isinstance(branch, (tuple, list)):
                msg = (
                    f"RunnableBranch branches must be "
                    f"tuples or lists, not {type(branch)}"
                )
                raise TypeError(msg)

            if len(branch) != _MIN_BRANCHES:
                msg = (
                    f"RunnableBranch branches must be "
                    f"tuples or lists of length 2, not {len(branch)}"
                )
                raise ValueError(msg)
            condition, runnable = branch
            condition = cast("Runnable[Input, bool]", coerce_to_runnable(condition))
            runnable = coerce_to_runnable(cast("Runnable[Input, Output]", runnable))
            branches_.append((condition, runnable))

        super().__init__(
            branches=branches_,
            default=default_,
        )

    model_config = ConfigDict(
        arbitrary_types_allowed=True,
    )

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return `True` as this class is serializable."""
        return True

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Ensure each branch tuple is exactly `(condition, runnable)`.
  2. Move per-branch options off the tuple: apply `.with_config(tags=[...])` or `.with_retry()` to the runnable inside the pair.
  3. Log `[(type(b), len(b) if isinstance(b, (tuple, list)) else None) for b in branches]` before construction to find the malformed pair.

Example fix

# before
branch = RunnableBranch((is_q, answer_fn, {"tags": ["q"]}), default)

# after
branch = RunnableBranch((is_q, answer_fn.with_config(tags=["q"])), default)
Defensive patterns

Strategy: validation

Validate before calling

bad = [b for b in branches if not (isinstance(b, (tuple, list)) and len(b) == 2)]
if bad:
    raise ValueError(f"malformed branches (need length-2 pairs): {bad!r}")

Type guard

from typing import TypeGuard

def is_length2_pair(b: object) -> TypeGuard[tuple]:
    return isinstance(b, tuple) and len(b) == 2

Prevention

When it happens

Trigger: Passing `(cond,)` (missing runnable), `(cond, fn, extra)` (extra element), or `(cond, fn, handler_kwargs)` where a config dict was mistakenly packed into the pair. Also destructuring bugs when branches are built with `zip()` of unequal lists.

Common situations: Trying to attach per-branch config/metadata as a third tuple element (not supported — use `.with_config()` on the runnable instead); building pairs with a loop bug that appends the wrong number of items; copy-paste leaving a trailing comma making a 1-tuple.

Related errors


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