{"record":{"id":"f0dc1a32eed198de","repo":"langchain-ai/deepagents","slug":"compiled-agent-does-not-expose-a-langgraph-tool-no","errorCode":null,"errorMessage":"Compiled agent does not expose a LangGraph tool node","messagePattern":"Compiled agent does not expose a LangGraph tool node","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/tool_catalog.py","lineNumber":257,"sourceCode":"\n    agent, _backend = create_cli_agent(\n        _CatalogModel(),\n        assistant_id=assistant_id,\n        tools=custom_tools,\n        enable_memory=False,\n        enable_skills=False,\n        enable_shell=True,\n        enable_interpreter=enable_interpreter,\n        fs_tools=fs_tools,\n        # Enumeration only -- this graph is never invoked. Enforcing\n        # `models.allowed` here would make a subagent that names a blocked\n        # model break `dcode tools list` instead of just being unusable.\n        enforce_model_policy=False,\n    )\n    tools = collect_tools_from_agent(agent)\n    if tools is None:\n        msg = \"Compiled agent does not expose a LangGraph tool node\"\n        raise RuntimeError(msg)\n    # Defensive backstop against a change in SDK behavior. The SDK's\n    # `FilesystemMiddleware` omits disallowed tools from the bound node\n    # entirely, so `collect_tools_from_agent` should already return only\n    # allowlisted filesystem tools. If a disallowed tool *does* leak through,\n    # enforcement broke on the real agent (this enumeration is built from the\n    # same `create_cli_agent` the runtime uses). Return the *unfiltered* list\n    # and log loudly rather than scrubbing: scrubbing would hide the one signal\n    # that enforcement failed and make `/tools` report a restricted surface over\n    # an unrestricted agent. (`None` — the unrestricted default — skips this.)\n    if isinstance(fs_tools, list):\n        enabled = frozenset(fs_tools)\n        leaked = [\n            tool.name\n            for tool in tools\n            if tool.name in _FILESYSTEM_TOOL_NAMES and tool.name not in enabled\n        ]\n        if leaked:\n            logger.error(","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/tool_catalog.py#L239-L275","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npip install -U langgraph langchain  # latest, may break internals\n// after\nuv sync --locked  # use the pinned lockfile versions","handlingStrategy":"try-catch","validationCode":"compiled = create_cli_agent(...)\nnodes = getattr(compiled, \"nodes\", None)\nif not nodes or not any(\"tool\" in str(n) for n in nodes):\n    raise RuntimeError(\"agent graph has no tool node; check SDK versions\")","typeGuard":"def has_tool_node(agent: object) -> TypeGuard[CompiledGraph]:\n    return getattr(agent, \"nodes\", None) is not None and any(\n        \"tools\" in str(name) for name in getattr(agent, \"nodes\", {})\n    )","tryCatchPattern":"try:\n    tools = collect_built_in_tools()\nexcept RuntimeError as exc:\n    logger.error(\"tool catalog unavailable: %s\", exc)\n    tools = []","preventionTips":["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."],"tags":["langgraph","sdk-incompatibility","tooling"],"backgroundTag":"agent-graph-structure-changed","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}