OpenBMB/ChatDev · error · ValueError

replacement must be a string

Error message

replacement must be a string

What it means

The optional replacement field defaults to "" but, when provided, must be a str. _normalize_edits rejects lists of lines, bytes, None, or numbers, because replacement is later processed with .splitlines(). Provide replacement as a single string (newline-separated if multi-line).

Source

Thrown at functions/function_calling/file.py:923

    for item in edits:
        if not isinstance(item, Mapping):
            raise ValueError("each edit entry must be a mapping object")
        try:
            start_line = int(item["start_line"])
        except (KeyError, TypeError, ValueError) as exc:
            raise ValueError("start_line is required for each edit") from exc
        end_line_raw = item.get("end_line", start_line)
        try:
            end_line = int(end_line_raw)
        except (TypeError, ValueError) as exc:
            raise ValueError("end_line must be an integer") from exc
        if start_line < 1:
            raise ValueError("start_line must be >= 1")
        if end_line < start_line - 1:
            raise ValueError("end_line must be >= start_line - 1")
        replacement = item.get("replacement", "")
        if not isinstance(replacement, str):
            raise ValueError("replacement must be a string")
        normalized.append(
            TextEdit(
                start_line=start_line,
                end_line=end_line,
                replacement_lines=replacement.splitlines(),
            )
        )

    normalized.sort(key=lambda edit: (edit.start_line, edit.end_line))
    _validate_edit_ranges(normalized)
    return normalized


def _build_single_edit(
    start_line: int,
    end_line: Optional[int],
    replacement: Optional[str],
) -> List[Mapping[str, Any]]:

View on GitHub (pinned to 4fb2db0ea9)

Solutions

  1. Join line lists into one string: "\n".join(lines)
  2. Use "" (or omit the key) for deletion instead of None
  3. Validate isinstance(replacement, str) per edit before calling

Example fix

# before
edits=[{"start_line": 1, "replacement": ["a", "b"]}]
# after
edits=[{"start_line": 1, "replacement": "a\nb"}]
Defensive patterns

Strategy: type-guard

Validate before calling

for e in edits:
    if isinstance(e.get("replacement"), list):
        e["replacement"] = "\n".join(e["replacement"])
    elif e.get("replacement") is None:
        e["replacement"] = ""
apply_text_edits(path=path, edits=edits)

Type guard

def replacements_are_strings(edits) -> bool:
    return all(
        not isinstance(e.get("replacement", ""), (list, dict, bytes, type(None)))
        for e in edits
    )

Try / catch

try:
    apply_text_edits(path=path, edits=edits)
except ValueError as e:
    if "replacement must be a string" in str(e):
        for ed in edits:
            if isinstance(ed.get("replacement"), list):
                ed["replacement"] = "\n".join(ed["replacement"])
        apply_text_edits(path=path, edits=edits)
    else:
        raise

Prevention

When it happens

Trigger: Passing replacement=["line1", "line2"] (list of lines); replacement=None; replacement=b"text"; replacement=42 from malformed JSON.

Common situations: Callers that already split text into lines for other APIs; JSON payloads where replacement is an array; treating an omitted replacement and null replacement as equivalent (null fails, omission defaults to "").

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of OpenBMB/ChatDev@4fb2db0ea9 (2026-08-27). Data as JSON: /api/errors/354b3782c852beff. Report an issue: GitHub.