{"record":{"id":"6daf22c1fef5d0b2","repo":"langchain-ai/deepagents","slug":"allow-fs-tools-must-include-read-file","errorCode":null,"errorMessage":"allow_fs_tools must include 'read_file'","messagePattern":"allow_fs_tools must include 'read_file'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/_server_config.py","lineNumber":497,"sourceCode":"        if self.sandbox_type == \"none\":\n            object.__setattr__(self, \"sandbox_type\", None)\n        if self.shell_allow_list is not None and len(self.shell_allow_list) == 0:\n            msg = \"shell_allow_list must be None or non-empty\"\n            raise ValueError(msg)\n        # `allow_fs_tools` is a security control: `None` means unrestricted, but\n        # an explicit list must be a usable allowlist. Own the non-empty +\n        # `read_file`-required invariant here (the single authoritative point\n        # for both the env round-trip via `from_env` and direct construction)\n        # rather than deferring to `FilesystemMiddleware`, which would only\n        # surface the violation a process boundary away. `_parse_allow_fs_tools_flag`\n        # still enforces the same rule at the CLI for a friendlier error.\n        if self.allow_fs_tools is not None:\n            if len(self.allow_fs_tools) == 0:\n                msg = \"allow_fs_tools must be None or a non-empty list\"\n                raise ValueError(msg)\n            if \"read_file\" not in self.allow_fs_tools:\n                msg = \"allow_fs_tools must include 'read_file'\"\n                raise ValueError(msg)\n        if isinstance(self.rubric_max_iterations, bool):\n            msg = \"rubric_max_iterations must be None or a positive integer\"\n            raise TypeError(msg)\n        if self.rubric_max_iterations is not None and self.rubric_max_iterations <= 0:\n            msg = \"rubric_max_iterations must be None or a positive integer\"\n            raise ValueError(msg)\n        if isinstance(self.cli_max_retries, bool):\n            msg = \"cli_max_retries must be None or a non-negative integer\"\n            raise TypeError(msg)\n        if self.cli_max_retries is not None and self.cli_max_retries < 0:\n            msg = \"cli_max_retries must be None or a non-negative integer\"\n            raise ValueError(msg)\n        if isinstance(self.recursion_limit, bool):\n            msg = \"recursion_limit must be None or a positive integer\"\n            raise TypeError(msg)\n        if self.recursion_limit is not None and self.recursion_limit <= 0:\n            msg = \"recursion_limit must be None or a positive integer\"\n            raise ValueError(msg)","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/_server_config.py#L479-L515","documentation":"ServerConfig's __post_init__ enforces that when allow_fs_tools is provided as a list, it must contain 'read_file' — the base filesystem permission all other fs tools depend on. The library rejects any non-empty allow_fs_tools list that omits 'read_file' so a config can never grant fs tools without read access. Pass None to disable all fs tools, or include 'read_file' when enabling any subset.","triggerScenarios":"Constructing ServerConfig (dataclass) with allow_fs_tools set to a non-empty list such as ['write_file'], ['ls', 'grep'], etc., that does not include 'read_file'. Validation runs in __post_init__, so the ValueError is raised immediately at construction.","commonSituations":"Tightening permissions in code review and removing 'read_file' while keeping write/edit tools; building the list programmatically from feature flags where read access is a separate flag that defaulted to off; copying a partial allowlist from another config or docs example.","solutions":["Add 'read_file' to the allow_fs_tools list, e.g. allow_fs_tools=['read_file', 'write_file']","If no filesystem access is intended, set allow_fs_tools=None instead of a partial list","Re-check any code that filters or builds the allowlist to ensure 'read_file' is never stripped out"],"exampleFix":"// before\nconfig = ServerConfig(allow_fs_tools=['write_file'])\n// after\nconfig = ServerConfig(allow_fs_tools=['read_file', 'write_file'])","handlingStrategy":"validation","validationCode":"def validate_allow_fs_tools(allow_fs_tools):\n    if allow_fs_tools is not None:\n        if len(allow_fs_tools) == 0:\n            raise ValueError(\"allow_fs_tools must be None or a non-empty list\")\n        if \"read_file\" not in allow_fs_tools:\n            raise ValueError(\"allow_fs_tools must include 'read_file'\")","typeGuard":"def is_valid_allow_fs_tools(v) -> bool:\n    return v is None or (isinstance(v, list) and len(v) > 0 and \"read_file\" in v)","tryCatchPattern":"try:\n    config = ServerConfig(allow_fs_tools=tools)\nexcept ValueError as e:\n    if \"allow_fs_tools\" in str(e):\n        config = ServerConfig(allow_fs_tools=[*tools, \"read_file\"])\n    else:\n        raise","preventionTips":["Always include 'read_file' as the first entry when enabling any fs tools","Use None, not [], to express 'no filesystem access'","Add a unit test asserting every shipped config preset includes 'read_file' when non-None"],"tags":["config-validation","valueerror","filesystem-tools"],"backgroundTag":"invalid-config-value","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}