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
- Cast provider IDs to str: str(channel_id)
- Flatten routing config into four scalar string fields
- 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
- Cast provider IDs (Slack channel, email address objects) to str early
- Keep routing config as four scalar strings
- Build records via constructors/to_dict
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
- 'delivery' must be an object or null
- 'delivery.{name}' must be a string or null
- 'delivery.attempts' must be a non-negative integer
- 'id', 'prompt', and 'schedule' must be strings
- 'consecutive_failures' must be a non-negative integer
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/6b0c41225f08a395.
Report an issue: GitHub.