MemPalace/mempalace · error · ValueError

{field_name} must be a non-empty string

Error message

{field_name} must be a non-empty string

What it means

ValueError raised by logstream's _sanitize_routing() when a required routing field (stream, room, agent, correlation_id) is None or the empty string. Required routing fields must carry a value before the stricter string/length/control-character checks run.

Source

Thrown at mempalace/logstream.py:108

    Uniqueness comes from the random suffix; ordering guarantees come
    from the rowid, so clock skew between writers cannot reorder or
    collide events.
    """
    stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S")
    return f"{prefix}_{stamp}_{secrets.token_hex(6)}"


def _sanitize_routing(value, field_name: str, required: bool = True) -> Optional[str]:
    """Validate a short routing field (stream, room, agent, correlation_id).

    Streams may contain ``/`` (``project/mempalace``), so this is looser
    than ``config.sanitize_name`` — but null bytes, control characters,
    and over-length values are still rejected.
    """
    if value is None or value == "":
        if required:
            raise ValueError(f"{field_name} must be a non-empty string")
        return None
    if not isinstance(value, str) or not value.strip():
        raise ValueError(f"{field_name} must be a non-empty string")
    value = strip_lone_surrogates(value.strip())
    if len(value) > _MAX_ROUTING_LENGTH:
        raise ValueError(f"{field_name} exceeds maximum length of {_MAX_ROUTING_LENGTH} characters")
    if any(ord(ch) < 0x20 or ch == "\x7f" for ch in value):
        raise ValueError(f"{field_name} contains control characters")
    return value


def _sanitize_event_type(value) -> str:
    if not isinstance(value, str) or not value.strip():
        raise ValueError("type must be a non-empty string")
    value = value.strip()
    if not _EVENT_TYPE_RE.match(value):
        raise ValueError(
            f"type={value!r} is not a valid event type "

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Supply a non-empty value for the named field
  2. If the field is genuinely optional in your flow, pass required=False so None/'' maps to None
  3. Generate correlation ids up front (the module provides stamp+token helpers) instead of passing None

Example fix

# before
events.emit(type="agent.turn.end", stream="", room="r1", agent="a1")  # ValueError: stream must be a non-empty string

# after
events.emit(type="agent.turn.end", stream="project/mempalace", room="r1", agent="a1")
Defensive patterns

Strategy: validation

Validate before calling

def require_routing(value, field):
    if value is None or value == "":
        raise ValueError(f"{field} must be a non-empty string")
    return value

stream = require_routing(stream, "stream")
correlation_id = require_routing(correlation_id or new_id(), "correlation_id")

Try / catch

try:
    events.emit(type=t, stream=s, room=r, agent=a)
except ValueError as e:
    if "must be a non-empty string" in str(e):
        # fill in the missing field from context and retry
        ...
    raise

Prevention

When it happens

Trigger: Calling the event-emission API with stream=None or correlation_id="" while the field is required (required=True, the default); a wrapper defaulting an argument to None instead of generating a value.

Common situations: Forgetting to pass correlation_id when correlating events; optional-looking kwargs that are actually required; code paths where the caller computes the stream name and the computation returns None.

Related errors


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/c7301550a4c07aac. Report an issue: GitHub.