langchain-ai/deepagents · error · ValueError

max_retries must be >= 0

Error message

max_retries must be >= 0

What it means

The constructor rejects negative `max_retries` with a `ValueError`, since a retry budget below zero is meaningless. The check runs after the bool guard, so only real integers can reach it.

Source

Thrown at libs/code/deepagents_code/model_retry.py:1070

            stream_output_is_visible: Whether message-stream chunks emitted by
                this model reach a user-visible consumer; it decides the
                `output_may_have_started` supersession flag on retry events.
                Keep `True` unless the entire nested stream is filtered before
                rendering.

        Raises:
            TypeError: If `max_retries` or `stream_output_is_visible` has the
                wrong type.
            ValueError: If `max_retries` is negative.
        """
        # `True >= 0` passes and `range(True + 1)` runs two attempts, so an
        # unchecked bool reads as a budget of one retry.
        if isinstance(max_retries, bool):
            msg = f"max_retries must be an int, got {type(max_retries).__name__}"
            raise TypeError(msg)
        if max_retries < 0:
            msg = "max_retries must be >= 0"
            raise ValueError(msg)
        if not isinstance(stream_output_is_visible, bool):
            msg = (
                "stream_output_is_visible must be a bool, got "
                f"{type(stream_output_is_visible).__name__}"
            )
            raise TypeError(msg)
        self.max_retries = max_retries
        self.stream_output_is_visible = stream_output_is_visible

    @staticmethod
    def _emit_stream_event(request: ModelRequest, event: dict[str, object]) -> None:
        writer = getattr(getattr(request, "runtime", None), "stream_writer", None)
        if writer is None:
            return
        try:
            writer(event)
        except GraphBubbleUp:
            # LangGraph control flow must not be mistaken for a writer fault.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Clamp the value before construction: `max_retries=max(0, computed)`
  2. Validate user/config input at load time and reject negatives early
  3. Default to 0 (no retries) when the computed budget would be negative

Example fix

// before
wrapped = RetryModel(inner, max_retries=depth - 1)
// after
wrapped = RetryModel(inner, max_retries=max(0, depth - 1))
Defensive patterns

Strategy: validation

Validate before calling

if not isinstance(max_retries, int) or isinstance(max_retries, bool) or max_retries < 0:
    raise ValueError("max_retries must be >= 0")

Try / catch

try:
    model = RetryModel(inner, max_retries=n)
except ValueError as e:
    logging.error("bad retry budget: %s", e)
    model = RetryModel(inner, max_retries=0)

Prevention

When it happens

Trigger: Calling the retry-model `__init__` with `max_retries=-1` or any negative integer, typically from arithmetic like `max_retries=depth - 1` where depth is 0, or a config parsed as negative.

Common situations: Off-by-one arithmetic when computing retries from a recursion depth; user-supplied config with a negative value; subtracting from 0 when retries are exhausted and re-wrapping.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/5183ba8bbb4e8dbb. Report an issue: GitHub.