langchain-ai/deepagents · error · ValueError
allow_fs_tools must be None or a non-empty list
Error message
allow_fs_tools must be None or a non-empty list
What it means
`ServerConfig.__post_init__` treats `allow_fs_tools` as a security control: `None` means unrestricted, but an explicit list must be usable as an allowlist — non-empty and containing `read_file` (so the agent can always read files). This `ValueError` fires for an explicitly empty list; the sibling check raises for a list missing `read_file`. It is enforced at the single authoritative construction point so violations surface immediately, not a process boundary away in `FilesystemMiddleware`.
Source
Thrown at libs/code/deepagents_code/_server_config.py:494
`allow_fs_tools` is an empty list or omits `"read_file"`, or
`rubric_max_iterations` / `recursion_limit` is non-positive.
"""
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)View on GitHub (pinned to a1af029e6e)
Solutions
- Pass `allow_fs_tools=None` to mean unrestricted filesystem tools
- Provide a non-empty list that includes "read_file", e.g. allow_fs_tools=["read_file","write_file"]
- Fix upstream code so an emptied list becomes `None` rather than `[]`
- Use the `--allow-fs-tools` CLI flag, which enforces the same rule earlier with a friendlier error (`_parse_allow_fs_tools_flag`)
Example fix
// before config = ServerConfig(allow_fs_tools=[], ...) # ValueError: allow_fs_tools must be None or a non-empty list // after config = ServerConfig(allow_fs_tools=None, ...) # or ["read_file", "glob"]
Defensive patterns
Strategy: validation
Validate before calling
def normalize_allow_fs_tools(tools: list[str] | None) -> list[str] | None:
if not tools:
return None # empty -> unrestricted, avoids the empty-list error
if "read_file" not in tools:
tools = ["read_file", *tools] # read_file is mandatory in any allowlist
return tools Type guard
def is_valid_allow_fs_tools(value: object) -> bool:
if value is None:
return True
return (
isinstance(value, list)
and len(value) > 0
and all(isinstance(t, str) for t in value)
and "read_file" in value
) Try / catch
try:
config = ServerConfig(**kwargs)
except ValueError as e:
if "allow_fs_tools" in str(e):
print(f"Invalid allow_fs_tools: {e}; pass None or a non-empty list including 'read_file'")
sys.exit(2)
raise Prevention
- Remember None means unrestricted and [] is never valid for this security control
- Always include "read_file" in any explicit allowlist (the library requires it)
- Prefer the --allow-fs-tools CLI flag, which validates the same rule earlier and more clearly
- When building the list programmatically, collapse empty results to None before constructing ServerConfig
When it happens
Trigger: Constructing `ServerConfig` with `allow_fs_tools=[]`, or setting `DEEPAGENTS_CODE_SERVER_ALLOW_FS_TOOLS` to a JSON array that round-trips to an empty list, then calling `from_env`/`from_cli_args`.
Common situations: Stripping all tools from a list programmatically and passing the result; an env deploy template rendering an empty array `[]`; code intending 'no restriction' that passes `[]` instead of `None`.
Related errors
- interpreter_ptc='all' exposes every host tool to PTC calls t
- -32602
- {what} must be absolute: {path}
- Home directory is not absolute: {launch_home}. Set $HOME to
- Cannot determine whether {str(left)!r} is {str(right)!r}: {e
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/2e47159aef4e23b7.
Report an issue: GitHub.