{"record":{"id":"f18f48065c0c4ded","repo":"langchain-ai/deepagents","slug":"shell-allow-list-must-be-none-or-non-empty","errorCode":null,"errorMessage":"shell_allow_list must be None or non-empty","messagePattern":"shell_allow_list must be None or non-empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/_server_config.py","lineNumber":483,"sourceCode":"        values.pop(\"PROJECT_ROOT\")\n        serialized = json.dumps(values, sort_keys=True, separators=(\",\", \":\"))\n        return hashlib.sha256(serialized.encode()).hexdigest()\n\n    def __post_init__(self) -> None:\n        \"\"\"Normalize fields and validate invariants.\n\n        Raises:\n            TypeError: If `rubric_max_iterations` or `recursion_limit` is a\n                boolean.\n            ValueError: If `shell_allow_list` is an empty list,\n                `allow_fs_tools` is an empty list or omits `\"read_file\"`, or\n                `rubric_max_iterations` / `recursion_limit` is non-positive.\n        \"\"\"\n        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:","sourceCodeStart":465,"sourceCodeEnd":501,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/_server_config.py#L465-L501","documentation":"`ServerConfig.__post_init__` enforces that `shell_allow_list` is either `None` (meaning unrestricted/default behavior) or a non-empty list of shell commands. An explicitly provided empty list `[]` is ambiguous and therefore rejected with this `ValueError` at construction time.","triggerScenarios":"Constructing `ServerConfig` (directly or via `from_env`/`from_cli_args`) with `shell_allow_list=[]`, or setting `DEEPAGENTS_CODE_SERVER_SHELL_ALLOW_LIST` such that it round-trips to an empty list.","commonSituations":"Code that starts with a default list, filters it, and ends up empty before passing it in; env/CLI parsing that produces `[]` from an empty or blank value; template configs rendering an empty placeholder.","solutions":["Pass `shell_allow_list=None` if you intend unrestricted/default shell behavior","Populate the list with at least one allowed command, e.g. shell_allow_list=[\"ls\",\"grep\"]","Fix upstream code so a fully-filtered list collapses to `None` instead of `[]`","If the value comes from the environment, unset `DEEPAGENTS_CODE_SERVER_SHELL_ALLOW_LIST` rather than setting it empty"],"exampleFix":"// before\nconfig = ServerConfig(shell_allow_list=[], ...)\n# ValueError: shell_allow_list must be None or non-empty\n// after\nconfig = ServerConfig(shell_allow_list=None, ...)  # or [\"ls\", \"cat\"]","handlingStrategy":"validation","validationCode":"def coerce_shell_allow_list(cmds: list[str] | None) -> list[str] | None:\n    return cmds if cmds else None  # empty list collapses to None before constructing ServerConfig","typeGuard":"def is_valid_shell_allow_list(value: object) -> bool:\n    return value is None or (isinstance(value, list) and len(value) > 0 and all(isinstance(c, str) for c in value))","tryCatchPattern":"try:\n    config = ServerConfig(**kwargs)\nexcept ValueError as e:\n    if \"shell_allow_list\" in str(e):\n        print(f\"Invalid shell_allow_list: {e}; pass None or a non-empty list\")\n        sys.exit(2)\n    raise","preventionTips":["Normalize empty lists to None at the boundary where config values are built","Never set list-valued env vars to empty values; unset them instead","Add a unit test that constructs ServerConfig with your real config-loading path","Filter-then-collapse pattern: after filtering a default list, substitute None if the result is empty"],"tags":["configuration","validation","dataclass","valueerror"],"backgroundTag":"empty-allowlist-not-allowed","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}