fix: handle GraphRecursionError as a graceful answer, not a 500#2
Merged
Conversation
If the supervisor keeps routing to a specialist without ever picking
FINISH (a genuinely reachable case — e.g. a question spanning many
domains, or the LLM bouncing indecisively between specialists),
langgraph raises GraphRecursionError once RECURSION_LIMIT is hit. The
/api/chat handler's blanket `except Exception` turned this into an
opaque 500 whose detail message leaks internal LangGraph wording
("recursion_limit config key", a docs URL) straight to the frontend's
error toast.
Catch GraphRecursionError specifically and return it as a normal
ChatResponse (200) with a clear, actionable message instead — this is
an expected, handled outcome of the hop budget, not a server fault.
Verified locally that looping topology genuinely raises
GraphRecursionError (not some other exception type) before writing the
fix. Added test_chat_handles_recursion_limit_gracefully, which stubs
the graph to raise it and asserts a 200 with the friendly message.
devthedevil
added a commit
that referenced
this pull request
Jul 22, 2026
fix: handle GraphRecursionError as a graceful answer, not a 500
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The supervisor↔worker loop is capped at
RECURSION_LIMIT = 8hops (agentstore/graph.py), which the README calls out as an intentional cost/latency bound. But if that budget is exhausted without the supervisor ever pickingFINISH— a genuinely reachable case for a question spanning several domains, or if the LLM bounces indecisively between specialists — langgraph raisesGraphRecursionError./api/chat's exception handling is a single blanket clause:So this expected, handled-in-principle outcome surfaces to the user as an opaque HTTP 500, with
detailcontaining raw LangGraph internals, e.g.:The bundled frontend (
frontend/index.html) renders any non-2xx response as a red error bubble, so a user who asks a legitimately complex question gets what looks like a broken app instead of a helpful nudge to rephrase.I verified this is actually reachable (not just theoretical) by building a small graph with the same topology as
graph.py(supervisor never returnsFINISH) and confirminglanggraph.errors.GraphRecursionError— not some other exception — is what gets raised at the configured limit.Fix
Catch
GraphRecursionErrorspecifically in the/api/chathandler and return it as a normalChatResponse(200) with a clear, actionable message, instead of falling through to the generic 500 handler:This keeps the existing
except Exceptionclause as the true "something actually broke" path, and gives this specific, expected outcome its own graceful handling — matching theChatResponsecontract the frontend already knows how to render as a normal chat bubble.Testing
test_chat_handles_recursion_limit_gracefully, which stubs the graph to raiseGraphRecursionErrorand asserts a200with the friendly message (mirrors the existing stub-graph pattern already used in this test file).ruff checkclean,pytest24/24 passing locally.