langchain-ai/deepagents · error · TypeError
File content must be a string or a legacy list of strings, g
Error message
File content must be a string or a legacy list of strings, got {type(content).__name__}. What it means
`_normalize_content` (used by `file_data_to_string` and grep matching) accepts file content as a `str` or the legacy `list[str]` line format, which it joins with newlines. Any other content type in a `FileData` dict raises a TypeError naming the actual type, protecting downstream string operations (grep, read) from bytes/dict/None content.
Source
Thrown at libs/deepagents/deepagents/backends/utils.py:196
def _normalize_content(file_data: FileData) -> str:
"""Normalize current and legacy file data content to a plain string.
Args:
file_data: `FileData` dict with `content` key.
Returns:
Content as a single string.
Raises:
TypeError: If content is neither a string nor a legacy list of strings.
"""
content: object = file_data["content"]
if isinstance(content, list) and all(isinstance(line, str) for line in content):
return "\n".join(content)
if not isinstance(content, str):
msg = f"File content must be a string or a legacy list of strings, got {type(content).__name__}."
raise TypeError(msg)
return content
def sanitize_tool_call_id(tool_call_id: str) -> str:
r"""Sanitize tool_call_id to prevent path traversal and separator issues.
Replaces dangerous characters (., /, \) with underscores.
"""
return tool_call_id.replace(".", "_").replace("/", "_").replace("\\", "_")
def format_content_with_line_numbers(
content: str | list[str],
start_line: int = 1,
) -> str:
"""Format file content with line numbers.
Chunks lines longer than `MAX_LINE_LENGTH` with continuation markersView on GitHub (pinned to a1af029e6e)
Solutions
- Have the custom backend decode content to `str` before building FileData
- Join lines client-side (`'\n'.join(lines)`) when your storage returns a list
- Skip or repair FileData entries with non-conforming content before grepping
- Normalize mixed lists with `[str(line) for line in lines]`
Example fix
// before
{'content': path.read_bytes(), ...}
// after
{'content': path.read_text(encoding='utf-8'), ...} Defensive patterns
Strategy: type-guard
Validate before calling
def to_file_data(path: str, raw) -> dict:
if isinstance(raw, bytes):
raw = raw.decode('utf-8')
if isinstance(raw, list) and not all(isinstance(l, str) for l in raw):
raw = [str(l) for l in raw]
if not isinstance(raw, str):
raw = str(raw)
return {'content': raw, 'encoding': 'utf-8'} Type guard
def is_stringable_file_content(content: object) -> bool:
if isinstance(content, str):
return True
return isinstance(content, list) and all(isinstance(l, str) for l in content) Try / catch
try:
text = file_data_to_string(file_data)
except TypeError as exc:
if 'must be a string or a legacy list of strings' in str(exc):
text = str(file_data['content'])
else:
raise Prevention
- Decode to str at the storage boundary (read_text, response.text) before building FileData
- Keep legacy list[str] line data homogeneous; normalize mixed lists on write
- Add a read-side assert/guard in custom backends before returning FileData
- Cover custom backends with a test asserting FileData content is str or list[str]
When it happens
Trigger: Providing `FileData` dicts with `content` set to bytes, dict, None, or a mixed list to backend utils that call `file_data_to_string` or `grep_matches_from_files`; custom backends returning non-string content from their own storage layer.
Common situations: Custom BackendProtocol implementations reading raw bytes from disk/S3; legacy store data migrated with mixed-type lists; hand-built FileData in tests.
Related errors
- interpreter_ptc must be False, 'safe', 'all', or a list of t
- model_retries must be an int, got {self.model_retries!r}
- cli_max_retries must be None or an int, got {self.cli_max_re
- Goal criteria request must be an object.
- Server '{server_name}' '{field_name}' must be a list of stri
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/07e44ac8fa036ef1.
Report an issue: GitHub.