{"record":{"id":"b92ed1446330f021","repo":"MemPalace/mempalace","slug":"content-must-be-a-non-empty-string-b92ed1","errorCode":null,"errorMessage":"content must be a non-empty string","messagePattern":"content must be a non-empty string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mempalace/logstream.py","lineNumber":526,"sourceCode":"        kind: str,\n        content: str,\n        created_by: str,\n        metadata: dict = None,\n    ) -> dict:\n        \"\"\"Store exact artifact content (v1: UTF-8 text only).\n\n        Returns the artifact record without echoing ``content`` back —\n        callers already hold the content; readers use :meth:`get_artifact`.\n        For ``kind=patch``, a ``warnings`` list is included when the diff\n        looks unappliable (missing trailing newline, CRLF endings); the\n        content itself is still stored verbatim.\n        \"\"\"\n        if not isinstance(kind, str) or kind not in ARTIFACT_KINDS:\n            allowed = \", \".join(sorted(ARTIFACT_KINDS))\n            raise ValueError(f\"kind={kind!r} is not one of: {allowed}\")\n        created_by = _sanitize_routing(created_by, \"created_by\")\n        if not isinstance(content, str) or not content:\n            raise ValueError(\"content must be a non-empty string\")\n        if \"\\x00\" in content:\n            raise ValueError(\"content contains null bytes\")\n        content = strip_lone_surrogates(content)\n        raw = content.encode(\"utf-8\")\n        if len(raw) > self.max_artifact_bytes:\n            raise ValueError(\n                f\"content is {len(raw)} bytes; maximum is {self.max_artifact_bytes} bytes\"\n            )\n        metadata_json = _sanitize_metadata(metadata)\n\n        artifact_id = _new_id(\"art\")\n        created_at = _utc_now_iso()\n        digest = sha256(raw).hexdigest()\n\n        with self._lock:\n            conn = self._conn()\n            with conn:\n                conn.execute(","sourceCodeStart":508,"sourceCodeEnd":544,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/logstream.py#L508-L544","documentation":"put_artifact requires `content` to be a non-empty str. Empty artifacts are meaningless (v1 stores exact UTF-8 text with a sha256 digest, and an empty payload has no content to verify), and None is not treated as ''. Binary or structured values are also rejected here since content must be text.","triggerScenarios":"put_artifact(kind='file', content='') after reading an empty file; content=None when a template render produced nothing; content=b'bytes'; content=[] from a failed subprocess capture.","commonSituations":"Reading a file that is empty or was deleted mid-run; a diff generator returning '' on no changes; subprocess.check_output errors swallowed and passed through as None.","solutions":["Skip the put_artifact call when content is falsy: if not content: return.","If the payload is genuinely empty by design, store a sentinel note ('(empty)') or record the fact in the event body instead.","Ensure text, not bytes: content=raw.decode('utf-8')."],"exampleFix":"// before\nart = ls.put_artifact(kind=\"file\", content=open(p).read(), ...)  # empty file\n// after\ncontent = open(p).read()\nif content:\n    art = ls.put_artifact(kind=\"file\", content=content, ...)\nelse:\n    art = None  # nothing to store","handlingStrategy":"validation","validationCode":"def storable_content(content) -> bool:\n    return isinstance(content, str) and bool(content)\n\nif not storable_content(content):\n    return None  # nothing to store; skip put_artifact entirely","typeGuard":"def is_storable_text(content) -> bool:\n    return isinstance(content, str) and len(content) > 0","tryCatchPattern":"try:\n    art = ls.put_artifact(kind=kind, content=content, ...)\nexcept ValueError as e:\n    if \"non-empty string\" in str(e):\n        return None  # treat empty payload as no-op\n    raise","preventionTips":["Short-circuit on falsy content before calling; an empty artifact is usually a bug upstream.","When rendering templates or diffs, assert non-empty output before storing."],"tags":["validation","logstream","artifact","empty-input"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}