Install
$ agentstack add skill-kirill-sviridov-agent-dev-skills-debug-langgraph-agent ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Debug LangGraph Agent
> The LangGraph-specific counterpart to superpowers:systematic-debugging, tuned to LangGraph symptoms. The goal is not to guess but to walk a checklist in the right order and localize the cause.
When to use
- Generated LangGraph code crashes with an error.
- The agent "works" but returns the wrong thing (loses context, loops, branches incorrectly).
- The symptom is unclear ("it just doesn't work").
- Before calling an external code-debugging MCP tool — first narrow the symptom down to one of the categories below.
When NOT to use
- It's not LangGraph code (a flat LLM call / create_agent without a graph — those have different ailments).
- The error is clearly in the code around the agent (FastAPI handler, DB query) — that's ordinary debugging, not a LangGraph specific.
§0. Look at the trace first
Before guessing from the code or classifying the symptom, look at what actually happened:
- LangSmith:
LANGSMITH_TRACING=true(+LANGSMITH_API_KEY) — every run is visible end to end: which nodes executed, with what state on input/output, which tool calls the model made, the full prompts. Half the sections below are resolved by a single glance at the trace. langgraph dev→ Studio: a visual run of the graph + time-travel — rewind to the checkpoint before the failing node, edit state by hand, fork the execution, and see whether the bug disappears.- Without a UI:
graph.get_state_history(config)— programmatic access to the same checkpoints: where it stopped, what was in state at each step.
If the trace is unavailable (no key, prod without LangSmith) — walk the checklist below.
Algorithm: classify the symptom → section
| Symptom | Where to look | |---------|---------------| | Messages "get lost" between nodes | §1 State reducer | | The graph doesn't take the expected branch | §2 Routing | | A tool "isn't called" / the wrong one is | §3 Tool wiring | | Hangs on one node / infinite loop | §4 Recursion limit + edges | | KeyError / TypeError in state | §5 State schema | | HITL doesn't return control after resume | §6 interrupt/checkpointer | | The LLM returns "garbage" (not JSON, extra text) | §7 Prompt + structured output | | A strange error about async / connection closed | §8 Async + checkpointer plumbing | | Broken after deploy, fine locally | §9 Environment |
§1. State reducer (messages / artifacts get lost)
A classic symptom in generated LangGraph code. Checks:
Annotated[list[AnyMessage], add_messages]—add_messagesis a function, not a string. Grep:Annotated.*["']add_messages["']→ if it matches, the string "reducer" is silently ignored and messages are overwritten.- Append lists (artifacts, results, sub_outputs): must have a reducer.
Annotated[list[X], operator.add]or a custom one. A barelist[X]with no reducer = overwrite: each node clobbers what previous ones accumulated. - Scalar (label, status): no reducer, overwritten — that's the norm.
- Dict-state: if keys go missing — check whether the node returns
{"key": value}instead of merging. Full replacement is occasionally a needed feature; usually it's a bug.
Test: add a print at the start and end of each node, see what comes in / goes out. Then compare.
§2. Routing (the graph goes the wrong way)
Command(goto=...)mixed withadd_conditional_edgeson the same node — two routing mechanisms conflict, behavior is unpredictable. Pick one.add_conditional_edgeswithout an explicit mapping — the third argument (the dict mapping) matters. Without it LangGraph tries to match the return value against node names literally, and"yes"doesn't find"approve".- The conditional routing function returns
Noneor a string that isn't in the edges → silent fall-through to default.
Test: graph.get_graph().draw_mermaid() — visually check where the arrows go.
§3. Tool wiring (a tool isn't called)
- The tool isn't passed to
create_agent(tools=[...])or toToolNode([...])— obvious, but common. - The tool docstring doesn't explain when to call it — the model doesn't understand the trigger. Docs should read like "Use this when the user asks about X."
- The tool signature uses complex types (e.g.
Optional[Union[X, Y]]) — the Anthropic/OpenAI schema may fail to generate. Simplify. - The tool raises an exception — a
ToolMessagewith the error lands in state, and the LLM can loop. Wrap the tool in try/except and return a human-readable error message as a normal return.
Test: after the first LLM call, print tool_calls on the AIMessage.
§4. Recursion / loops
recursion_limitdefaults to 25. For long ReAct chains that's too low. Raise it:app.invoke(..., {"recursion_limit": 50}).- An infinite loop tool → llm → the same tool — usually the prompt doesn't explain when to stop. Add to the system prompt: "when all the information is gathered — answer the user directly without calling tools."
- A conditional edge always returns the same value — a bug in the routing function.
- A node genuinely hangs (slow tool, stuck HTTP) — with LangGraph 1.2+ set a timeout:
add_node(..., timeout=60)ortimeout=TimeoutPolicy(run_timeout=120, idle_timeout=30). On expiry aNodeTimeoutErroris raised (retryable by default). Works only for async nodes — a sync node withtimeout=is rejected at compile time.
§5. State schema mismatch
KeyErrorwhen reading state in a node — the field isn't declared in the TypedDict.invoke({...})with extra keys — keys outside the schema are silently dropped or break nodes. Runtime metadata (userid, threadid) →config["configurable"], not state.- Pydantic state vs TypedDict — both are fine, but you can't mix them in one graph.
§6. interrupt / checkpointer / resume
interrupt()without acheckpointer— interrupt requires the graph to be compiled with a checkpointer. Without one it raises a RuntimeError.Command(resume=value)without athread_id— resume goes to a foreign/new thread, and the original one stays suspended.- You changed the state schema but the old checkpoint remains — deserialization breaks. Clear the checkpoints or migrate.
durability='exit'+ interrupt — the checkpoint is written only on exit from the graph, so an interrupt inside the graph isn't persisted and HITL silently breaks (there's nothing to resume). For HITL usedurability='sync'or'async'(the default).
§7. Prompt / structured output
- The LLM wraps output in markdown fences — JSON arrives inside ```
`json ...```` and the parser fails. Either forbid it explicitly in the prompt, or strip defensively. response_format=with a string / made-up type — valid options: a Pydantic class directly (response_format=MySchema— auto-selects ProviderStrategy if the model supports native structured output; often the best choice) or an explicitToolStrategy(MySchema)for models without native support. Strings like"json"don't work.- A Pydantic schema with complex
Union/Discriminator— models generate these poorly. Simplify.
§8. Async / checkpointer plumbing
AsyncPostgresSavermust live inside a long-lived context manager (async within the startup lifespan). If it's opened inside a request handler, the connection closes before resume, and resuming a day later breaks.asyncio.run(...)inside a nested loop (e.g. in FastAPI) —RuntimeError: This event loop is already running. Useawaitdirectly.- Mixing sync
.invoke()and async.ainvoke()in one graph — may work, but confuses interrupt handling.
§9. Environment
- Works locally, not in Docker — environment variables (
OPENAI_API_KEY,OPENAI_BASE_URL) aren't passed into the container. - CPU-only server vs local GPU — if an embedding model is used locally (BGE/MiniLM), everything works on CPU but slowly — a timeout kills the nodes.
- DNS / firewall — the LLM provider or proxy is unreachable from the container.
curlfrom inside the container → the check.
If nothing helped
- Extract a minimal reproducible slice (one graph, no FastAPI/DB scaffolding).
- Call an external code-debugging MCP tool if you have one (optional — this assumes a local LangGraph-debugging MCP tool; without it, keep working the checklist and the minimal repro by hand).
search_agent_knowledge("")(optional — assumes a local knowledge-retrieval MCP tool; skip if unavailable) — the RAG store may already have a card with the fix.- If the symptom is about a feature from a recent 1.x release (see the changelog on docs.langchain.com) that isn't in RAG — search the web (and add it to your knowledge store if you keep one).
Debugging anti-patterns
- Immediately rewriting "from scratch, differently" without localizing the cause. You waste time, learn nothing, and the same bug arrives in a new wrapper.
- Testing with a real LLM on every iteration. Expensive, slow, non-deterministic. For state/edge logic use
GenericFakeChatModel/FakeMessagesListChatModelor mocked responses. - Ignoring LangGraph warnings about deprecated APIs — that's often where your bug is (
set_entry_pointstill works, but conditional_edges around it behave oddly).
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kirill-sviridov
- Source: kirill-sviridov/agent-dev-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.