{"record":{"id":"734a985ca5afe15d","repo":"langchain-ai/deepagents","slug":"cannot-enforce-allow-fs-tools-on-compiled-subage","errorCode":null,"errorMessage":"Cannot enforce --allow-fs-tools on compiled subagent {subagent.get('name', '<unnamed>')!r}: its middleware is not configurable, so the filesystem restriction would be silently bypassed.","messagePattern":"Cannot enforce --allow-fs-tools on compiled subagent (.+?): its middleware is not configurable, so the filesystem restriction would be silently bypassed\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/agent.py","lineNumber":262,"sourceCode":"    Raises:\n        ValueError: If a `CompiledSubAgent` (identified by a `\"runnable\"` key,\n            matching the SDK's own `\"runnable\" in spec` discriminator in\n            `deepagents.middleware.subagents`) is present. Such a spec is used\n            as-is by the SDK and its `middleware`\n            key is never read, so we cannot enforce the restriction on it. dcode\n            adds only raw `SubAgent` dicts today, but the declared type admits\n            compiled specs: fail loud rather than silently exposing an\n            unrestricted filesystem via `task` delegation.\n    \"\"\"\n    for subagent in custom_subagents:\n        if \"runnable\" in subagent:\n            msg = (\n                \"Cannot enforce --allow-fs-tools on compiled subagent \"\n                f\"{subagent.get('name', '<unnamed>')!r}: its middleware is \"\n                \"not configurable, so the filesystem restriction would be \"\n                \"silently bypassed.\"\n            )\n            raise ValueError(msg)\n        # `\"runnable\" in subagent` above narrows the union to `SubAgent`.\n        subagent_tool_descriptions = (\n            _get_harness_tool_descriptions(subagent[\"model\"])\n            if \"model\" in subagent\n            else main_tool_descriptions\n        )\n        subagent[\"middleware\"] = cast(\n            \"list[AgentMiddleware]\",\n            [\n                *subagent.get(\"middleware\", []),\n                FilesystemMiddleware(\n                    backend=backend,\n                    tools=fs_tools,\n                    custom_tool_descriptions=subagent_tool_descriptions,\n                ),\n            ],\n        )\n","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/agent.py#L244-L280","documentation":"When --allow-fs-tools is used, create_cli_agent injects filesystem restrictions into each subagent's middleware. A subagent supplied as an already-compiled graph (the 'runnable' branch of the SubAgent union) has no configurable middleware, so the restriction could not be applied — the code raises instead of silently allowing the subagent to bypass the filesystem restriction.","triggerScenarios":"Calling create_cli_agent with --allow-fs-tools enabled and a subagents list containing a compiled subagent (a dict with a 'runnable' key, e.g. {'name': ..., 'runnable': compiled_graph}). Also reproduced by the test test_compiled_subagent_raises_rather_than_bypassing.","commonSituations":"Users composing pre-compiled LangGraph subagents for performance/reuse while simultaneously trying to sandbox filesystem tool access; copying agent definitions that embed compiled graphs.","solutions":["Replace the compiled subagent with a declarative SubAgent dict (name/model/prompt/tools/middleware) so _inject_fs_tools_into_subagents can configure its middleware.","If the subagent must stay compiled, wrap or rebuild its graph with the equivalent filesystem-restricting middleware yourself before passing it in.","Disable --allow-fs-tools only if you can guarantee the compiled subagent exposes no unrestricted filesystem tools; otherwise it is not a valid alternative."],"exampleFix":"# before\nsubagents = [{\"name\": \"researcher\", \"runnable\": compiled_graph}]\ncreate_cli_agent(subagents=subagents, allow_fs_tools=True)\n# after\nsubagents = [{\"name\": \"researcher\", \"model\": \"...\", \"prompt\": \"...\"}]\ncreate_cli_agent(subagents=subagents, allow_fs_tools=True)","handlingStrategy":"validation","validationCode":"def assert_fs_tools_enforceable(subagents, allow_fs_tools):\n    if not allow_fs_tools:\n        return\n    for s in subagents:\n        if \"runnable\" in s:\n            raise ValueError(\n                f\"Cannot enforce --allow-fs-tools on compiled subagent {s.get('name', '<unnamed>')!r}; \"\n                \"use a declarative subagent instead\"\n            )","typeGuard":"def is_declarative_subagent(s: dict) -> bool:\n    return \"runnable\" not in s and \"name\" in s","tryCatchPattern":"try:\n    agent = create_cli_agent(subagents=subagents, allow_fs_tools=True)\nexcept ValueError as exc:\n    if \"compiled subagent\" in str(exc):\n        subagents = [declarative_form(s) for s in subagents]\n        agent = create_cli_agent(subagents=subagents, allow_fs_tools=True)\n    else:\n        raise","preventionTips":["When using --allow-fs-tools, always pass declarative subagent dicts, never compiled graphs.","If you need pre-compiled graphs, apply the filesystem-restricting middleware inside the graph yourself and document it.","Audit subagent tool lists for unrestricted filesystem tools before enabling sandboxed runs."],"tags":["agent","security","subagents","config"],"backgroundTag":"subagent-not-configurable","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}