HKUDS/Vibe-Trading · error · TypeError

'{name}' must be a string or null

Error message

'{name}' must be a string or null

What it means

Raised by Job from_dict (agent/src/scheduled_research/models.py:498) when any of the delivery routing fields — delivery_channel, delivery_target, delivery_target_ref, delivery_target_label — is present but not a string and not null. These fields select where run results are delivered; each must be str|null.

Source

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

            raise ValueError("'source_type' must be 'prompt' or 'playbook'")
        if playbook_slug is not None and not isinstance(playbook_slug, str):
            raise TypeError("'playbook_slug' must be a string or null")
        if end_at is not None and (isinstance(end_at, bool) or not isinstance(end_at, int)):
            raise TypeError("'end_at' must be an integer (epoch ms) or null")
        raw_config = data.get("config")
        config: Dict[str, Any] = raw_config if isinstance(raw_config, dict) else {}
        delivery_channel = data.get("delivery_channel")
        delivery_target = data.get("delivery_target")
        delivery_target_ref = data.get("delivery_target_ref")
        delivery_target_label = data.get("delivery_target_label")
        for name, value in (
            ("delivery_channel", delivery_channel),
            ("delivery_target", delivery_target),
            ("delivery_target_ref", delivery_target_ref),
            ("delivery_target_label", delivery_target_label),
        ):
            if value is not None and not isinstance(value, str):
                raise TypeError(f"'{name}' must be a string or null")
        return cls(
            id=job_id,
            prompt=prompt,
            schedule=schedule,
            title=title,
            source_type=source_type,
            playbook_slug=playbook_slug,
            end_at=end_at,
            next_run_at=next_run_at,
            status=status,
            created_at=created_at,
            last_run_at=last_run_at,
            consecutive_failures=consecutive_failures,
            last_error=last_error,
            failure_kind=failure_kind,
            config=config,
            timezone=tz,
            delivery_channel=delivery_channel,

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Cast provider IDs to str: str(channel_id)
  2. Flatten routing config into four scalar string fields
  3. Omit unused routing fields rather than sending empty containers

Example fix

// before
record["delivery_target"] = channel["id"]  # may be int

// after
record["delivery_target"] = str(channel["id"])
Defensive patterns

Strategy: validation

Validate before calling

ROUTING = ("delivery_channel", "delivery_target", "delivery_target_ref", "delivery_target_label")

def routing_ok(d):
    return all(d.get(k) is None or isinstance(d.get(k), str) for k in ROUTING)

Type guard

ROUTING = ("delivery_channel", "delivery_target", "delivery_target_ref", "delivery_target_label")

def sanitized_routing(data):
    out = dict(data)
    for k in ROUTING:
        v = out.get(k)
        if v is not None and not isinstance(v, str):
            out[k] = str(v)
    return out

Try / catch

try:
    job = Job.from_dict(record)
except TypeError as exc:
    if "must be a string or null" in str(exc):
        record = dict(record)
        for k in ("delivery_channel", "delivery_target", "delivery_target_ref", "delivery_target_label"):
            if record.get(k) is not None and not isinstance(record[k], str):
                record[k] = str(record[k])
        job = Job.from_dict(record)
    else:
        raise

Prevention

When it happens

Trigger: {"delivery_channel": "slack"} is fine, but {"delivery_channel": ["slack"]}, {"delivery_target": 12345}, or a dict in delivery_target_label raises.

Common situations: Structured delivery configs (channel objects) flattened incorrectly; numeric chat/channel IDs from provider APIs stored uncast; schema drift when adding the ref/label fields.

Related errors


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