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 markers

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Have the custom backend decode content to `str` before building FileData
  2. Join lines client-side (`'\n'.join(lines)`) when your storage returns a list
  3. Skip or repair FileData entries with non-conforming content before grepping
  4. 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

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


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