HKUDS/Vibe-Trading · error · TypeError

'delivery.{name}' must be a string or null

Error message

'delivery.{name}' must be a string or null

What it means

Raised by delivery from_dict (agent/src/scheduled_research/models.py:283) when one of the string fields session_id, key, error, or provider_message_id inside the serialized 'delivery' object is present but not a string and not null. The deserializer enforces these fields are str|null since they are identifiers and human-readable error text.

Source

Thrown at agent/src/scheduled_research/models.py:283

            The reconstructed :class:`DeliveryRecord`.

        Raises:
            TypeError: If a present field has the wrong type.
            ValueError: If ``status`` is not a recognized value.
        """
        if not data:
            return cls()
        if not isinstance(data, dict):
            raise TypeError("'delivery' must be an object or null")
        raw_status = data.get("status", DeliveryStatus.NONE.value)
        try:
            status = DeliveryStatus(raw_status)
        except ValueError as exc:
            raise ValueError(f"unknown delivery status {raw_status!r}") from exc
        for name in ("session_id", "key", "error", "provider_message_id"):
            value = data.get(name)
            if value is not None and not isinstance(value, str):
                raise TypeError(f"'delivery.{name}' must be a string or null")
        updated_at = data.get("updated_at")
        if updated_at is not None and not isinstance(updated_at, int):
            raise TypeError("'delivery.updated_at' must be an integer (epoch ms) or null")
        attempts = data.get("attempts", 0)
        if isinstance(attempts, bool) or not isinstance(attempts, int) or attempts < 0:
            raise TypeError("'delivery.attempts' must be a non-negative integer")
        return cls(
            status=status,
            session_id=data.get("session_id"),
            key=data.get("key"),
            error=data.get("error"),
            attempts=attempts,
            updated_at=updated_at,
            provider_message_id=data.get("provider_message_id"),
        )


# ---------------------------------------------------------------------------

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Coerce identifiers to str before persisting: str(session_id)
  2. Flatten error info into a single string message
  3. Omit fields you don't have rather than sending empty dicts/lists

Example fix

// before
delivery = {"session_id": 12345, "status": "sent"}

// after
delivery = {"session_id": str(12345), "status": "sent"}
Defensive patterns

Strategy: validation

Validate before calling

STR_FIELDS = ("session_id", "key", "error", "provider_message_id")

def delivery_strings_ok(d):
    return d is None or isinstance(d, dict) and all(
        d.get(k) is None or isinstance(d.get(k), str) for k in STR_FIELDS
    )

Type guard

from typing import Any, Dict

def sanitized_delivery(data: Dict[str, Any]) -> Dict[str, Any]:
    out = dict(data)
    for k in ("session_id", "key", "error", "provider_message_id"):
        v = out.get(k)
        if v is not None and not isinstance(v, str):
            out[k] = str(v)
    return out

Try / catch

try:
    delivery = DeliveryInfo.from_dict(raw)
except TypeError:
    delivery = DeliveryInfo()  # or log the raw record and quarantine it

Prevention

When it happens

Trigger: delivery = {"session_id": 12345} (numeric ID from a DB), {"key": ["a"]}, or {"error": {"msg": "x"}}. Omitted keys default to None and are fine.

Common situations: Coercing IDs to integers when building the payload; nested error objects instead of a formatted message string; schema drift after changing what these fields hold.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/616a9a2fada5d140. Report an issue: GitHub.