{"record":{"id":"f8893d469a746392","repo":"MemPalace/mempalace","slug":"field-name-must-be-a-string-f8893d","errorCode":null,"errorMessage":"{field_name} must be a string","messagePattern":"(.+?) must be a string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mempalace/logstream.py","lineNumber":146,"sourceCode":"        )\n    return value\n\n\ndef _sanitize_status(value) -> Optional[str]:\n    if value is None or value == \"\":\n        return None\n    if not isinstance(value, str) or value not in EVENT_STATUSES:\n        allowed = \", \".join(sorted(EVENT_STATUSES))\n        raise ValueError(f\"status={value!r} is not one of: {allowed}\")\n    return value\n\n\ndef _sanitize_body(value, max_bytes: int, field_name: str = \"body\") -> str:\n    \"\"\"Validate verbatim payload text. Empty is allowed; ``None`` becomes ''.\"\"\"\n    if value is None:\n        return \"\"\n    if not isinstance(value, str):\n        raise ValueError(f\"{field_name} must be a string\")\n    if \"\\x00\" in value:\n        raise ValueError(f\"{field_name} contains null bytes\")\n    value = strip_lone_surrogates(value)\n    size = len(value.encode(\"utf-8\"))\n    if size > max_bytes:\n        raise ValueError(f\"{field_name} is {size} bytes; maximum is {max_bytes} bytes\")\n    return value\n\n\ndef _sanitize_metadata(value) -> str:\n    \"\"\"Validate optional metadata dict and return its canonical JSON text.\"\"\"\n    if value is None:\n        return \"{}\"\n    if not isinstance(value, dict):\n        raise ValueError(\"metadata must be an object\")\n    try:\n        encoded = json.dumps(value, ensure_ascii=False, sort_keys=True)\n    except (TypeError, ValueError) as exc:","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/logstream.py#L128-L164","documentation":"_sanitize_body requires the event `body` (and any field validated through it) to be a Python str. None is accepted and normalized to '', but any other type — int, dict, list, bytes — raises this error before storage. The body is a verbatim UTF-8 text payload, so binary or structured values are rejected at the boundary.","triggerScenarios":"append_event(body={'text': 'fix ranking'}) (dict); body=12345; body=b'raw bytes'; ack_event(body=['line1','line2']). Note body='' and body=None are both fine.","commonSituations":"Callers serializing structured payloads and forgetting json.dumps(); MCP tool handlers passing through JSON objects unmodified; code that previously wrote bytes from a file read in 'rb' mode.","solutions":["If the payload is structured, serialize it yourself: body=json.dumps(payload, ensure_ascii=False).","If it came from a file, open in text mode or decode explicitly: body=data.decode('utf-8').","For an empty body, pass body=None or body='' rather than 0 or {}. "],"exampleFix":"// before\nevt = ls.append_event(type=\"task.request\", body={\"task\": \"fix ranking\"}, ...)\n// after\nimport json\nevt = ls.append_event(type=\"task.request\", body=json.dumps({\"task\": \"fix ranking\"}, ensure_ascii=False), ...)","handlingStrategy":"type-guard","validationCode":"def coerce_body(body):\n    if body is None:\n        return \"\"\n    if isinstance(body, bytes):\n        return body.decode(\"utf-8\")\n    if isinstance(body, (dict, list)):\n        return json.dumps(body, ensure_ascii=False)\n    if not isinstance(body, str):\n        raise TypeError(f\"body must be text, got {type(body).__name__}\")\n    return body\n\nbody = coerce_body(raw_payload)","typeGuard":"def is_valid_body(b) -> bool:\n    return b is None or isinstance(b, str)","tryCatchPattern":"try:\n    evt = ls.append_event(..., body=body)\nexcept ValueError as e:\n    if \"must be a string\" in str(e) and not isinstance(body, str):\n        evt = ls.append_event(..., body=json.dumps(body, ensure_ascii=False))\n    else:\n        raise","preventionTips":["Serialize structured payloads with json.dumps at the call site, not inside a catch-all handler.","Open files in text mode ('r') when their contents become bodies.","Type-annotate producer functions (body: str) so mypy catches dict/bytes flows."],"tags":["validation","logstream","type-error","body"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}