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
- Join line lists into one string: "\n".join(lines)
- Use "" (or omit the key) for deletion instead of None
- 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
- Pass replacement as one newline-joined string
- Use "" for deletions, not None
- Schema: replacement must be type string, not array
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
- each edit entry must be a mapping object
- end_line must be an integer
- at least one edit instruction is required
- start_line is required for each edit
- end_line must be >= start_line - 1
AI-assisted analysis of OpenBMB/ChatDev@4fb2db0ea9 (2026-08-27).
Data as JSON: /api/errors/354b3782c852beff.
Report an issue: GitHub.