langchain-ai/deepagents · error · ValueError
allow_fs_tools must include 'read_file'
Error message
allow_fs_tools must include 'read_file'
What it means
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.
Source
Thrown at libs/code/deepagents_code/_server_config.py:497
if self.sandbox_type == "none":
object.__setattr__(self, "sandbox_type", None)
if self.shell_allow_list is not None and len(self.shell_allow_list) == 0:
msg = "shell_allow_list must be None or non-empty"
raise ValueError(msg)
# `allow_fs_tools` is a security control: `None` means unrestricted, but
# an explicit list must be a usable allowlist. Own the non-empty +
# `read_file`-required invariant here (the single authoritative point
# for both the env round-trip via `from_env` and direct construction)
# rather than deferring to `FilesystemMiddleware`, which would only
# surface the violation a process boundary away. `_parse_allow_fs_tools_flag`
# still enforces the same rule at the CLI for a friendlier error.
if self.allow_fs_tools is not None:
if len(self.allow_fs_tools) == 0:
msg = "allow_fs_tools must be None or a non-empty list"
raise ValueError(msg)
if "read_file" not in self.allow_fs_tools:
msg = "allow_fs_tools must include 'read_file'"
raise ValueError(msg)
if isinstance(self.rubric_max_iterations, bool):
msg = "rubric_max_iterations must be None or a positive integer"
raise TypeError(msg)
if self.rubric_max_iterations is not None and self.rubric_max_iterations <= 0:
msg = "rubric_max_iterations must be None or a positive integer"
raise ValueError(msg)
if isinstance(self.cli_max_retries, bool):
msg = "cli_max_retries must be None or a non-negative integer"
raise TypeError(msg)
if self.cli_max_retries is not None and self.cli_max_retries < 0:
msg = "cli_max_retries must be None or a non-negative integer"
raise ValueError(msg)
if isinstance(self.recursion_limit, bool):
msg = "recursion_limit must be None or a positive integer"
raise TypeError(msg)
if self.recursion_limit is not None and self.recursion_limit <= 0:
msg = "recursion_limit must be None or a positive integer"
raise ValueError(msg)View on GitHub (pinned to a1af029e6e)
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
Example fix
// before config = ServerConfig(allow_fs_tools=['write_file']) // after config = ServerConfig(allow_fs_tools=['read_file', 'write_file'])
Defensive patterns
Strategy: validation
Validate before calling
def validate_allow_fs_tools(allow_fs_tools):
if allow_fs_tools is not None:
if len(allow_fs_tools) == 0:
raise ValueError("allow_fs_tools must be None or a non-empty list")
if "read_file" not in allow_fs_tools:
raise ValueError("allow_fs_tools must include 'read_file'") Type guard
def is_valid_allow_fs_tools(v) -> bool:
return v is None or (isinstance(v, list) and len(v) > 0 and "read_file" in v) Try / catch
try:
config = ServerConfig(allow_fs_tools=tools)
except ValueError as e:
if "allow_fs_tools" in str(e):
config = ServerConfig(allow_fs_tools=[*tools, "read_file"])
else:
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- rubric_max_iterations must be None or a positive integer
- cli_max_retries must be None or a non-negative integer
- Could not parse embedded resource block. Block expected eith
- question text must not be blank
- choice has a blank 'value': {choice!r}
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/6daf22c1fef5d0b2.
Report an issue: GitHub.