langchain-ai/deepagents · error · ValueError

Invalid {SERVER_ENV_PREFIX}{suffix}: expected a JSON string

Error message

Invalid {SERVER_ENV_PREFIX}{suffix}: expected a JSON string list

What it means

`_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.

Source

Thrown at libs/code/deepagents_code/_server_config.py:80

        return None
    try:
        return json.loads(raw)
    except json.JSONDecodeError as exc:
        msg = (
            f"Failed to parse {SERVER_ENV_PREFIX}{suffix} as JSON: {exc}. "
            f"Value was: {raw[:200]!r}"
        )
        raise ValueError(msg) from exc


def _read_env_str_list(suffix: str) -> tuple[str, ...]:
    raw = _read_env_json(suffix)
    if raw is None:
        return ()
    if isinstance(raw, list) and all(isinstance(item, str) for item in raw):
        return tuple(raw)
    msg = f"Invalid {SERVER_ENV_PREFIX}{suffix}: expected a JSON string list"
    raise ValueError(msg)


def _read_env_allow_fs_tools() -> list[FsToolName] | None:
    """Read and shape-validate the `ALLOW_FS_TOOLS` filesystem allowlist.

    The parent writes only an absent variable (unrestricted — `None`, which is
    also what `--allow-fs-tools all` collapses to) or a non-empty JSON list of
    tool names (`main._parse_allow_fs_tools_flag`). This runs in the server
    subprocess, where the variable could be tampered with, so — because the
    value is a security control — any unrecognized shape must fail closed
    (raise) rather than fall through to an unrestricted filesystem.
    (`_read_env_json` already fails closed on malformed JSON.)

    `[]` and unknown tool names are rejected here, not deferred downstream, so
    the returned list genuinely satisfies `list[FsToolName]` and the `cast`
    asserts membership that was actually checked. Importing `deepagents` here is
    fine: the subprocess already imports the SDK to build the agent (this is not
    the arg-parsing hot path guarded in `main`). The `"read_file"` requirement

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Set the variable to a JSON array of strings: `export DEEPAGENTS_CODE_SERVER_<SUFFIX>='["item1","item2"]'`.
  2. 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)"`.
  3. If the variable should not be set at all, unset it rather than exporting an empty string or non-list JSON value.
  4. Fix the writer side to use `json.dumps(["item1", "item2"])` before placing the value in the environment.

Example fix

// before (shell)
export DEEPAGENTS_CODE_SERVER_EXTRA_TOOLS="grep,glob"
// after (shell)
export DEEPAGENTS_CODE_SERVER_EXTRA_TOOLS='["grep","glob"]'
Defensive patterns

Strategy: type-guard

Validate before calling

import json, os

def env_str_list_valid(suffix: str) -> bool:
    raw = os.environ.get(f"DEEPAGENTS_CODE_SERVER_{suffix}")
    if raw is None:
        return True
    try:
        value = json.loads(raw)
    except json.JSONDecodeError:
        return False
    return isinstance(value, list) and all(isinstance(i, str) for i in value)

Type guard

import json
from typing import TypeGuard

def is_str_list(value: object) -> TypeGuard[list[str]]:
    return isinstance(value, list) and all(isinstance(i, str) for i in value)

Try / catch

try:
    config = ServerConfig.from_env()
except ValueError as exc:
    if "expected a JSON string list" in str(exc):
        raise ConfigError(f"re-export variable as a JSON string array: {exc}") from exc
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/dfce5d124540a1c4. Report an issue: GitHub.