AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

ModelRetryMiddleware/ToolRetryMiddleware `Retry_on=SomeError` Retries Every Exception

In langchain's retry middleware, `should_retry_exception` checks `callable(retry_on)` before `isinstance`, treating exception classes as callables. Passing a single exception class (e.g. `TimeoutError`) invokes the class with the exception, returning a truthy exception instance, causing every exception to be retried regardless of the intended filter.

highConfidence 95%LangchainAffected Vlangchain==1.4.0Affected Vlangchain-Core==1.6.3

Origin Analysis

The function `should_retry_exception` currently has `if callable(retry_on): return retry_on(exc)` before the `isinstance` branch. Exception classes are callable, so `retry_on(exc)` creates an exception instance, which is always truthy, making the function return `True` for all exceptions. The type-based branch is never reached for single exception classes.
Run the following Python snippet: `from langchain.agents.middleware._retry import should_retry_exception; print(should_retry_exception(KeyError('k'), ValueError))` -- this returns an exception instance (truthy) instead of `False`. Then using `ToolRetryMiddleware(max_retries=2, retry_on=TimeoutError)` with a tool that raises `KeyError` will retry the full budget instead of failing immediately.

Fixing Code Block

Edge Case Audit

This is a behavior change: code that previously relied on the buggy always-retry behavior will now fail fast as intended. Validate that all callers pass a type, tuple, or predicate; no other uses are expected. If custom callables depended on being invoked for every exception, they will no longer be called unless specified explicitly (which is correct). Rollback: revert to the original callable-first ordering if unforeseen issues arise, but this reintroduces the bug.

Ecosystem Topology