langchain-ai/deepagents · error · RuntimeError
Compiled agent does not expose a LangGraph tool node
Error message
Compiled agent does not expose a LangGraph tool node
What it means
collect_built_in_tools builds the CLI tool catalog from a compiled agent created via create_cli_agent, then calls collect_tools_from_agent. If the compiled LangGraph agent does not expose its tool node, the function returns None and this RuntimeError is raised rather than silently emitting an empty catalog — an SDK/LangGraph structural change should break `dcode tools list` loudly.
Source
Thrown at libs/code/deepagents_code/tool_catalog.py:257
agent, _backend = create_cli_agent(
_CatalogModel(),
assistant_id=assistant_id,
tools=custom_tools,
enable_memory=False,
enable_skills=False,
enable_shell=True,
enable_interpreter=enable_interpreter,
fs_tools=fs_tools,
# Enumeration only -- this graph is never invoked. Enforcing
# `models.allowed` here would make a subagent that names a blocked
# model break `dcode tools list` instead of just being unusable.
enforce_model_policy=False,
)
tools = collect_tools_from_agent(agent)
if tools is None:
msg = "Compiled agent does not expose a LangGraph tool node"
raise RuntimeError(msg)
# Defensive backstop against a change in SDK behavior. The SDK's
# `FilesystemMiddleware` omits disallowed tools from the bound node
# entirely, so `collect_tools_from_agent` should already return only
# allowlisted filesystem tools. If a disallowed tool *does* leak through,
# enforcement broke on the real agent (this enumeration is built from the
# same `create_cli_agent` the runtime uses). Return the *unfiltered* list
# and log loudly rather than scrubbing: scrubbing would hide the one signal
# that enforcement failed and make `/tools` report a restricted surface over
# an unrestricted agent. (`None` — the unrestricted default — skips this.)
if isinstance(fs_tools, list):
enabled = frozenset(fs_tools)
leaked = [
tool.name
for tool in tools
if tool.name in _FILESYSTEM_TOOL_NAMES and tool.name not in enabled
]
if leaked:
logger.error(View on GitHub (pinned to a1af029e6e)
Solutions
- Pin/align langgraph and deepagents versions to those the SDK expects, then reinstall.
- Inspect the compiled agent's graph nodes and update collect_tools_from_agent for the new structure.
- Verify the agent is created via create_cli_agent with tool binding enabled (enforce_model_policy=False here does not disable tools).
- If a mock is used in tests, make it mimic the real compiled graph's tool node.
Example fix
// before pip install -U langgraph langchain # latest, may break internals // after uv sync --locked # use the pinned lockfile versions
Defensive patterns
Strategy: try-catch
Validate before calling
compiled = create_cli_agent(...)
nodes = getattr(compiled, "nodes", None)
if not nodes or not any("tool" in str(n) for n in nodes):
raise RuntimeError("agent graph has no tool node; check SDK versions") Type guard
def has_tool_node(agent: object) -> TypeGuard[CompiledGraph]:
return getattr(agent, "nodes", None) is not None and any(
"tools" in str(name) for name in getattr(agent, "nodes", {})
) Try / catch
try:
tools = collect_built_in_tools()
except RuntimeError as exc:
logger.error("tool catalog unavailable: %s", exc)
tools = [] Prevention
- Pin langgraph/langchain/deepagents to the lockfile versions.
- Run `dcode tools list` in CI after dependency bumps to catch graph changes early.
- Keep mocks of create_cli_agent faithful to the real graph structure.
When it happens
Trigger: Calling collect_built_in_tools (or collect_catalog) with a LangGraph/SDK version where collect_tools_from_agent can no longer find the tool node on the compiled graph — e.g. after upgrading langchain/deepagents where graph internals changed, or passing an agent compiled without tool binding.
Common situations: Dependency upgrades (langchain-core, langgraph, deepagents) that rename or restructure the tool node; constructing the agent with middleware config that suppresses tool binding; mocking create_cli_agent with a plain object in tests.
Related errors
- Server process exited with code {self._process.returncode}
- Server graph '{graph_name}' failed readiness check (status:
- thread_id is required in config.configurable
- Pending graph work remained on thread {thread_id} after clea
- LangGraph returned non-object thread state.
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/f0dc1a32eed198de.
Report an issue: GitHub.