langchain-ai/deepagents · error · TypeError
`{field_name}` must be a list/set of strings, got {type(valu
Error message
`{field_name}` must be a list/set of strings, got {type(value).__name__} What it means
_coerce_frozen_strset validates set-typed fields (excluded_tools, excluded_middleware) loaded from dicts. If the value is not a list, tuple, set, or frozenset (and not None), a TypeError is raised naming the field and its actual type. The result is normalized to a frozenset of strings.
Source
Thrown at libs/deepagents/deepagents/profiles/harness/harness_profiles.py:849
if not isinstance(value, Mapping):
msg = f"`{field_name}` must be a mapping, got {type(value).__name__}"
raise TypeError(msg)
out: dict[str, str] = {}
for key, val in value.items():
if not isinstance(key, str) or not isinstance(val, str):
msg = f"`{field_name}` keys and values must be strings"
raise TypeError(msg)
out[key] = val
return out
def _coerce_frozen_strset(value: object, field_name: str) -> frozenset[str]:
"""Validate that `value` is an iterable of strings (or `None`)."""
if value is None:
return frozenset()
if not isinstance(value, (list, tuple, set, frozenset)):
msg = f"`{field_name}` must be a list/set of strings, got {type(value).__name__}"
raise TypeError(msg)
entries: list[str] = []
for entry in value:
if not isinstance(entry, str):
msg = f"`{field_name}` entries must be strings, got {type(entry).__name__} ({entry!r})"
raise TypeError(msg)
entries.append(entry)
return frozenset(entries)
def _coerce_general_purpose_subagent(value: object) -> GeneralPurposeSubagentProfile | None:
"""Validate and construct a `GeneralPurposeSubagentProfile` from a dict value."""
if value is None:
return None
if isinstance(value, Mapping):
return GeneralPurposeSubagentProfile.from_dict(cast("Mapping[str, Any]", value))
msg = f"`general_purpose_subagent` must be a mapping, got {type(value).__name__}"
raise TypeError(msg)
View on GitHub (pinned to a1af029e6e)
Solutions
- Wrap the value in a list: ["bash"] instead of "bash".
- Split comma-joined strings before passing: value.split(",").
- Use a set/frozenset directly if building programmatically.
Example fix
// before
config = HarnessProfileConfig.from_dict({"excluded_tools": "bash"})
// after
config = HarnessProfileConfig.from_dict({"excluded_tools": ["bash"]}) Defensive patterns
Strategy: type-guard
Validate before calling
tools = data.get("excluded_tools")
if isinstance(tools, str):
data["excluded_tools"] = tools.split(",") if "," in tools else [tools] Type guard
def is_str_iterable(value: object) -> TypeGuard[list[str] | set[str] | tuple[str, ...] | frozenset[str]]:
return isinstance(value, (list, tuple, set, frozenset)) and all(isinstance(e, str) for e in value) Try / catch
try:
config = HarnessProfileConfig.from_dict(data)
except TypeError as e:
if "must be a list/set of strings" in str(e):
field = str(e).split('`')[1]
data[field] = [data[field]]
config = HarnessProfileConfig.from_dict(data)
else:
raise Prevention
- Always write exclusion sets as lists in config files, even for one entry.
- Never pass a bare string where a set field is expected.
- Normalize comma-joined values to lists before from_dict.
When it happens
Trigger: Calling HarnessProfileConfig.from_dict with e.g. {"excluded_tools": "bash"} (bare string instead of a list) or {"excluded_tools": {"bash": true}} (dict).
Common situations: YAML/JSON configs where a single excluded tool was written as a plain string instead of a one-element list; string-joined values like "bash,git"; config builders assigning dict membership maps.
Related errors
- `{field_name}` entries must be strings, got {type(entry).__n
- interpreter_ptc must be False, 'safe', 'all', or a list of t
- max_retries must be an int, got {type(max_retries).__name__}
- stream_output_is_visible must be a bool, got {type(stream_ou
- context.{key} must be a string or null, got {type(value).__n
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/4f4da239aff0e3bc.
Report an issue: GitHub.