Skip to content

fix(langchain): re-raise non-retryable exceptions in ModelRetryMiddleware - #38960

Merged
ccurme (ccurme) merged 4 commits into
langchain-ai:masterfrom
Yigtwxx:Yigtwxx/langchain/propagate-non-retryable-model-exceptions
Aug 18, 2026
Merged

fix(langchain): re-raise non-retryable exceptions in ModelRetryMiddleware#38960
ccurme (ccurme) merged 4 commits into
langchain-ai:masterfrom
Yigtwxx:Yigtwxx/langchain/propagate-non-retryable-model-exceptions

Conversation

@Yigtwxx

Copy link
Copy Markdown
Contributor

Fixes #38893


Setting retry_on=(RateLimitError,) is the natural way to say "retry transient API failures, surface everything else." ToolRetryMiddleware honors that: since #38845 an exception that doesn't match retry_on is re-raised immediately, and #38884 documented the contract — "Exceptions that do not match propagate immediately and are not handled by on_failure."

ModelRetryMiddleware never received that change. It still routes non-matching exceptions into _handle_failure, which under the default on_failure="continue" converts them into a plain error AIMessage. So a TypeError from a callback or a custom wrap_model_call middleware becomes an assistant turn reading Model call failed after 1 attempt with TypeError: ..., and the agent proceeds on that fabricated answer instead of failing.

Two things follow from this. First, errors the user deliberately excluded from retry are swallowed exactly when they most need to surface — a TypeError usually means the pipeline is structurally broken, and the error message ends up occupying the slot where the model's answer should be, structurally indistinguishable from a real response downstream. Second, retry_on means opposite things on the two sides: narrowing it surfaces errors for tools and hides them for models, same package, same parameter name, same documented contract.

This mirrors #38845 on the model side — the two _handle_failure calls in the non-retryable branch become a bare raise, in both wrap_model_call and awrap_model_call — and aligns the retry_on docstring with the wording #38884 established. ModelFallbackMiddleware doesn't use retry_on and is out of scope.

This is a behavior change for anyone relying on on_failure to absorb non-retryable errors, but it is the same change maintainers already accepted on the tool side, and #38884 states this contract for both.

Tests mirror the structure of the #38845 tests: sync and async cases asserting a non-matching exception propagates on the first attempt and is not routed through on_failure, including the on_failure="continue" path where the message is fabricated today.

Release note

ModelRetryMiddleware now propagates exceptions that do not match retry_on instead of converting them into an error AIMessage, matching ToolRetryMiddleware's behavior and the documented retry_on contract. Non-retryable exceptions are no longer handled by on_failure.


This contribution was developed with assistance from an AI coding agent (Claude Code); all changes were reviewed and verified by the author.

…eware`

`ModelRetryMiddleware` routed exceptions that do not match `retry_on` into
`_handle_failure`, which under the default `on_failure="continue"` converted
them into an error `AIMessage` — so an error the user deliberately excluded
from retrying was swallowed and the agent continued on a fabricated turn.

langchain-ai#38845 changed this exact branch on the tool side to re-raise, and langchain-ai#38884
documented the contract: exceptions that do not match `retry_on` propagate
immediately and are not handled by `on_failure`. The model side kept the
pre-langchain-ai#38845 code, so `retry_on` meant opposite things for tools and models.

This mirrors langchain-ai#38845 in both the sync and async paths and aligns the
`retry_on` docstring with langchain-ai#38884.

Fixes langchain-ai#38893

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThW31LHCRsNnUgVgUrQeB9
@github-actions github-actions Bot added fix For PRs that implement a fix langchain `langchain` package issues & PRs size: S 50-199 LOC labels Jul 19, 2026
@github-actions

This comment has been minimized.

@codspeed-hq

codspeed-hq Bot commented Aug 6, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 2 untouched benchmarks
⏩ 15 skipped benchmarks1


Comparing Yigtwxx:Yigtwxx/langchain/propagate-non-retryable-model-exceptions (8df042f) with master (c8b2d76)2

Open in CodSpeed

Footnotes

  1. 15 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on master (f368888) during the generation of this report, so c8b2d76 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Raja Gopal (Rajagopalhertzian) added a commit to Rajagopalhertzian/langchain that referenced this pull request Aug 14, 2026
Closes langchain-ai#38960

- ModelRetryMiddleware now re-raises non-retryable exceptions immediately
  instead of swallowing them via on_failure='continue'
- Matches ToolRetryMiddleware behavior and retry_on contract (langchain-ai#38884)
- Updated tests to reflect new behavior
@ccurme
ccurme (ccurme) merged commit e92c6db into langchain-ai:master Aug 18, 2026
56 checks passed
@Yigtwxx
Yiğit ERDOĞAN (Yigtwxx) deleted the Yigtwxx/langchain/propagate-non-retryable-model-exceptions branch August 19, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external fix For PRs that implement a fix langchain `langchain` package issues & PRs new-contributor size: S 50-199 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ModelRetryMiddleware converts non-retryable exceptions into an error AIMessageToolRetryMiddleware propagates them since #38845

2 participants