{"record":{"id":"44d3a7d8725cdd98","repo":"microsoft/autogen","slug":"expected-str-or-bytes-got-type-new-content","errorCode":null,"errorMessage":"Expected str or bytes, got {type(new_content)}","messagePattern":"Expected str or bytes, got (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/packages/autogen-ext/src/autogen_ext/memory/canvas/_text_canvas.py","lineNumber":113,"sourceCode":"    # ----------------------------------------------------------------------------------\n    # BaseCanvas interface implementation\n    # ----------------------------------------------------------------------------------\n\n    def list_files(self) -> Dict[str, int]:\n        \"\"\"Return a mapping of *filename → latest revision number*.\"\"\"\n        return {fname: revs[-1].revision for fname, revs in self._files.items() if revs}\n\n    def get_latest_content(self, filename: str) -> str:  # noqa: D401 – keep API identical\n        \"\"\"Return the most recent content or an empty string if the file is new.\"\"\"\n        revs = self._files.get(filename, [])\n        return revs[-1].content if revs else \"\"\n\n    def add_or_update_file(self, filename: str, new_content: Union[str, bytes, Any]) -> None:\n        \"\"\"Create *filename* or append a new revision containing *new_content*.\"\"\"\n        if isinstance(new_content, bytes):\n            new_content = new_content.decode(\"utf-8\")\n        if not isinstance(new_content, str):\n            raise ValueError(f\"Expected str or bytes, got {type(new_content)}\")\n        if filename not in self._files:\n            self._files[filename] = [FileRevision(new_content, 1)]\n        else:\n            last_rev_num = self._files[filename][-1].revision\n            self._files[filename].append(FileRevision(new_content, last_rev_num + 1))\n\n    def get_diff(self, filename: str, from_revision: int, to_revision: int) -> str:\n        \"\"\"Return a unified diff between *from_revision* and *to_revision*.\"\"\"\n        revisions = self._files.get(filename, [])\n        if not revisions:\n            return \"\"\n        # Fetch the contents for the requested revisions.\n        from_content = self.get_revision_content(filename, from_revision)\n        to_content = self.get_revision_content(filename, to_revision)\n        if from_content == \"\" and to_content == \"\":  # one (or both) revision ids not found\n            return \"\"\n        diff = difflib.unified_diff(\n            from_content.splitlines(keepends=True),","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-ext/src/autogen_ext/memory/canvas/_text_canvas.py#L95-L131","documentation":"add_or_update_file accepts only str or bytes (bytes is UTF-8 decoded first). Any other type (int, dict, list, None) is rejected with this ValueError to keep every revision a clean string.","triggerScenarios":"Calling add_or_update_file(filename, 123), add_or_update_file(filename, {'a': 1}), or passing None/Path objects as content.","commonSituations":"LLM tool calls returning non-string payloads; upstream code passing parsed JSON or numbers directly; forgetting to serialize before storage.","solutions":["Serialize non-string content to a string before calling: json.dumps for dicts/lists, str() for scalars.","If you passed bytes with a non-UTF-8 encoding, decode manually first."],"exampleFix":"# before\ncanvas.add_or_update_file(\"data.json\", {\"a\": 1})\n# after\ncanvas.add_or_update_file(\"data.json\", json.dumps({\"a\": 1}))","handlingStrategy":"type-guard","validationCode":"import json\nif not isinstance(content, (str, bytes)):\n    content = json.dumps(content)  # or str(content) for scalars\ncanvas.add_or_update_file(filename, content)","typeGuard":"def is_canvas_writable_content(value) -> bool:\n    return isinstance(value, (str, bytes))","tryCatchPattern":null,"preventionTips":["Serialize at the boundary: json.dumps any structured payload before it reaches the canvas.","Type the parameter as str in your own wrappers so mypy catches misuse."],"tags":["autogen","canvas","type-validation","json"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}