Skip to content

fix: handle GraphRecursionError as a graceful answer, not a 500#2

Merged
devthedevil merged 2 commits into
mainfrom
fix/graceful-recursion-limit
Jul 7, 2026
Merged

fix: handle GraphRecursionError as a graceful answer, not a 500#2
devthedevil merged 2 commits into
mainfrom
fix/graceful-recursion-limit

Conversation

@devthedevil

Copy link
Copy Markdown
Owner

Problem

The supervisor↔worker loop is capped at RECURSION_LIMIT = 8 hops (agentstore/graph.py), which the README calls out as an intentional cost/latency bound. But if that budget is exhausted without the supervisor ever picking FINISH — a genuinely reachable case for a question spanning several domains, or if the LLM bounces indecisively between specialists — langgraph raises GraphRecursionError.

/api/chat's exception handling is a single blanket clause:

except Exception as exc:  # noqa: BLE001 — bubble up as 500 with a short msg
    raise HTTPException(status_code=500, detail=f"graph error: {exc}") from exc

So this expected, handled-in-principle outcome surfaces to the user as an opaque HTTP 500, with detail containing raw LangGraph internals, e.g.:

graph error: Recursion limit of 8 reached without hitting a stop condition. You can increase the limit by setting the recursion_limit config key. For troubleshooting, visit: https://docs.la...

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 returns FINISH) and confirming langgraph.errors.GraphRecursionError — not some other exception — is what gets raised at the configured limit.

Fix

Catch GraphRecursionError specifically in the /api/chat handler and return it as a normal ChatResponse (200) with a clear, actionable message, instead of falling through to the generic 500 handler:

except GraphRecursionError:
    return ChatResponse(
        answer=RECURSION_LIMIT_MESSAGE,
        agent="supervisor",
        latency_ms=int((time.perf_counter() - start) * 1000),
    )

This keeps the existing except Exception clause as the true "something actually broke" path, and gives this specific, expected outcome its own graceful handling — matching the ChatResponse contract the frontend already knows how to render as a normal chat bubble.

Testing

  • Added test_chat_handles_recursion_limit_gracefully, which stubs the graph to raise GraphRecursionError and asserts a 200 with the friendly message (mirrors the existing stub-graph pattern already used in this test file).
  • Full suite: ruff check clean, pytest 24/24 passing locally.

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
devthedevil merged commit 19f36a5 into main Jul 7, 2026
2 checks passed
@devthedevil
devthedevil deleted the fix/graceful-recursion-limit branch July 7, 2026 00:26
devthedevil added a commit that referenced this pull request Jul 22, 2026
fix: handle GraphRecursionError as a graceful answer, not a 500
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant