{"record":{"id":"3a46b0b34733e2af","repo":"MemPalace/mempalace","slug":"content-is-len-raw-bytes-maximum-is-self-max","errorCode":null,"errorMessage":"content is {len(raw)} bytes; maximum is {self.max_artifact_bytes} bytes","messagePattern":"content is (.+?) bytes; maximum is (.+?) bytes","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mempalace/logstream.py","lineNumber":532,"sourceCode":"\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(\n                    \"INSERT INTO artifacts (id, kind, sha256, size_bytes, content,\"\n                    \" created_by, created_at, metadata_json, origin_replica)\"\n                    \" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\",\n                    (\n                        artifact_id,\n                        kind,","sourceCodeStart":514,"sourceCodeEnd":550,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/logstream.py#L514-L550","documentation":"The UTF-8 encoding of the artifact content exceeds max_artifact_bytes (default 4 MiB, settable via the Logstream constructor). The logstream stores artifacts verbatim with explicit limits and never truncates, so an oversized write is refused. The byte count in the message is the true encoded size, which matters for multi-byte scripts.","triggerScenarios":"put_artifact(kind='file', content=huge_file_text) over 4,194,304 bytes; a large patch generated across a whole repo; UTF-8 text whose character count is under 4M but byte count over it; replicas constructed with different max_artifact_bytes values.","commonSituations":"Whole-repo diffs or bundled logs attached as one artifact; no chunking strategy in the producer; the limit lowered in config while old tooling still emits monolith artifacts.","solutions":["Split the content into multiple artifacts below the cap and link them from the event (artifact_ids accepts a list).","Pre-check size: if len(content.encode('utf-8')) > ls.max_artifact_bytes: split or compress.","If genuinely needed, raise the limit on every replica: Logstream(db_path=..., max_artifact_bytes=8*1024*1024)."],"exampleFix":"// before\nart = ls.put_artifact(kind=\"patch\", content=whole_repo_diff, ...)\n// after\nchunks = [whole_repo_diff[i:i+1_000_000] for i in range(0, len(whole_repo_diff), 1_000_000)]\narts = [ls.put_artifact(kind=\"patch\", content=c, created_by=\"mac-codex\") for c in chunks]\nevt = ls.append_event(..., artifact_ids=[a[\"id\"] for a in arts])","handlingStrategy":"validation","validationCode":"def fits_artifact(ls, content: str) -> bool:\n    return len(content.encode(\"utf-8\")) <= ls.max_artifact_bytes\n\nif not fits_artifact(ls, content):\n    chunks = [content[i:i+1_000_000] for i in range(0, len(content), 1_000_000)]\n    arts = [ls.put_artifact(kind=kind, content=c, created_by=who) for c in chunks]\n    content, artifact_ids = None, [a[\"id\"] for a in arts]","typeGuard":"def artifact_within_limit(ls, content) -> bool:\n    return isinstance(content, str) and len(content.encode(\"utf-8\")) <= ls.max_artifact_bytes","tryCatchPattern":"try:\n    art = ls.put_artifact(kind=kind, content=content, ...)\nexcept ValueError as e:\n    if \"maximum is\" in str(e) and \"bytes\" in str(e):\n        mid = len(content) // 2\n        a1 = ls.put_artifact(kind=kind, content=content[:mid], ...)\n        a2 = ls.put_artifact(kind=kind, content=content[mid:], ...)\n        art = None  # reference both ids from the event\n    else:\n        raise","preventionTips":["Measure UTF-8 bytes, not characters, before creating large artifacts.","Split monolithic diffs/logs into per-file artifacts so chunks stay far under 4 MiB.","Keep max_artifact_bytes consistent across replicas that exchange artifacts."],"tags":["validation","logstream","artifact","size-limit"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}