{"record":{"id":"dfce5d124540a1c4","repo":"langchain-ai/deepagents","slug":"invalid-server-env-prefix-suffix-expected-a-js","errorCode":null,"errorMessage":"Invalid {SERVER_ENV_PREFIX}{suffix}: expected a JSON string list","messagePattern":"Invalid (.+?)(.+?): expected a JSON string list","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/_server_config.py","lineNumber":80,"sourceCode":"        return None\n    try:\n        return json.loads(raw)\n    except json.JSONDecodeError as exc:\n        msg = (\n            f\"Failed to parse {SERVER_ENV_PREFIX}{suffix} as JSON: {exc}. \"\n            f\"Value was: {raw[:200]!r}\"\n        )\n        raise ValueError(msg) from exc\n\n\ndef _read_env_str_list(suffix: str) -> tuple[str, ...]:\n    raw = _read_env_json(suffix)\n    if raw is None:\n        return ()\n    if isinstance(raw, list) and all(isinstance(item, str) for item in raw):\n        return tuple(raw)\n    msg = f\"Invalid {SERVER_ENV_PREFIX}{suffix}: expected a JSON string list\"\n    raise ValueError(msg)\n\n\ndef _read_env_allow_fs_tools() -> list[FsToolName] | None:\n    \"\"\"Read and shape-validate the `ALLOW_FS_TOOLS` filesystem allowlist.\n\n    The parent writes only an absent variable (unrestricted — `None`, which is\n    also what `--allow-fs-tools all` collapses to) or a non-empty JSON list of\n    tool names (`main._parse_allow_fs_tools_flag`). This runs in the server\n    subprocess, where the variable could be tampered with, so — because the\n    value is a security control — any unrecognized shape must fail closed\n    (raise) rather than fall through to an unrestricted filesystem.\n    (`_read_env_json` already fails closed on malformed JSON.)\n\n    `[]` and unknown tool names are rejected here, not deferred downstream, so\n    the returned list genuinely satisfies `list[FsToolName]` and the `cast`\n    asserts membership that was actually checked. Importing `deepagents` here is\n    fine: the subprocess already imports the SDK to build the agent (this is not\n    the arg-parsing hot path guarded in `main`). The `\"read_file\"` requirement","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/_server_config.py#L62-L98","documentation":"`_read_env_str_list` raises this `ValueError` when a `DEEPAGENTS_CODE_SERVER_*` variable parses as valid JSON (so error 38 did not fire) but the parsed value is not a list of strings — it may be a JSON string, number, object, a list of non-strings, or an empty/mixed list depending on the caller. The server expects a JSON array of string values for list-shaped configuration, and it deliberately fails closed rather than coercing.","triggerScenarios":"Setting a list-shaped `DEEPAGENTS_CODE_SERVER_*` variable (consumed via `_read_env_str_list` from `from_env`) to `'\"grep,glob\"'` (a JSON string, not array), `'[1,2]'`, `'{\"a\":1}'`, `'true'`, or `[]` where a non-empty list of tool names is required downstream.","commonSituations":"Users exporting a comma-separated string thinking it will be split; quoting mistakes turning the intended array into a single JSON string; a parent process writing a JSON object instead of an array; CI templating that renders `[\"a\",\"b\"]` with escaped quotes stripped, yielding a string value.","solutions":["Set the variable to a JSON array of strings: `export DEEPAGENTS_CODE_SERVER_<SUFFIX>='[\"item1\",\"item2\"]'`.","Verify the shape: run `python -c \"import json,os;v=json.loads(os.environ['DEEPAGENTS_CODE_SERVER_<SUFFIX>']);assert isinstance(v,list) and all(isinstance(i,str) for i in v)\"`.","If the variable should not be set at all, unset it rather than exporting an empty string or non-list JSON value.","Fix the writer side to use `json.dumps([\"item1\", \"item2\"])` before placing the value in the environment."],"exampleFix":"// before (shell)\nexport DEEPAGENTS_CODE_SERVER_EXTRA_TOOLS=\"grep,glob\"\n// after (shell)\nexport DEEPAGENTS_CODE_SERVER_EXTRA_TOOLS='[\"grep\",\"glob\"]'","handlingStrategy":"type-guard","validationCode":"import json, os\n\ndef env_str_list_valid(suffix: str) -> bool:\n    raw = os.environ.get(f\"DEEPAGENTS_CODE_SERVER_{suffix}\")\n    if raw is None:\n        return True\n    try:\n        value = json.loads(raw)\n    except json.JSONDecodeError:\n        return False\n    return isinstance(value, list) and all(isinstance(i, str) for i in value)","typeGuard":"import json\nfrom typing import TypeGuard\n\ndef is_str_list(value: object) -> TypeGuard[list[str]]:\n    return isinstance(value, list) and all(isinstance(i, str) for i in value)","tryCatchPattern":"try:\n    config = ServerConfig.from_env()\nexcept ValueError as exc:\n    if \"expected a JSON string list\" in str(exc):\n        raise ConfigError(f\"re-export variable as a JSON string array: {exc}\") from exc\n    raise","preventionTips":["Export list config as '[\"a\",\"b\"]' — an array of quoted strings, not a CSV string.","Remember a bare JSON string or object fails this check even though it is valid JSON.","Generate values with json.dumps(list_of_strings) on the writer side.","Prefer unsetting the variable over setting an empty or wrong-shaped placeholder."],"tags":["json","environment","type-validation","configuration"],"backgroundTag":"invalid-env-json","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}