HKUDS/Vibe-Trading · error · ValueError
'source_type' must be 'prompt' or 'playbook'
Error message
'source_type' must be 'prompt' or 'playbook'
What it means
Raised by Job from_dict (agent/src/scheduled_research/models.py:480) when 'source_type' is not exactly 'prompt' or 'playbook'. This distinguishes jobs created from a raw prompt string from those rendered from a playbook template; the default is 'prompt' when absent.
Source
Thrown at agent/src/scheduled_research/models.py:480
# non-string values all normalize to None.
raw_tz = data.get("timezone")
tz = raw_tz if isinstance(raw_tz, str) and raw_tz.strip() else None
if raw_tz is not None and tz is None:
logger.warning(
"scheduled research job %s has an unusable timezone %r; "
"evaluating its schedule in UTC",
job_id,
raw_tz,
)
status = JobStatus(data["status"])
title = data.get("title", "")
source_type = data.get("source_type", "prompt")
playbook_slug = data.get("playbook_slug")
end_at = data.get("end_at")
if not isinstance(title, str):
raise TypeError("'title' must be a string")
if source_type not in {"prompt", "playbook"}:
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")View on GitHub (pinned to 80ffdda44c)
Solutions
- Use exact literals 'prompt' or 'playbook'
- Default/omit the key when unsure — absent means 'prompt'
- Normalize case at your write boundary: source_type.lower()
Example fix
// before record["source_type"] = "Playbook" // after record["source_type"] = "playbook"
Defensive patterns
Strategy: validation
Validate before calling
SOURCE_TYPES = {"prompt", "playbook"}
def source_type_ok(v):
return v in SOURCE_TYPES # note: absent defaults to 'prompt' Type guard
SOURCE_TYPES = {"prompt", "playbook"}
def safe_source_type(v):
if isinstance(v, str) and v.lower() in SOURCE_TYPES:
return v.lower()
return "prompt" Try / catch
try:
job = Job.from_dict(record)
except ValueError as exc:
if "source_type" in str(exc):
record = dict(record); record["source_type"] = "prompt"
job = Job.from_dict(record)
else:
raise Prevention
- Write source_type from a constant, never free text
- Omit the key when it equals the default
- Extend the set in models first when adding new source kinds
When it happens
Trigger: {"source_type": "Playbook"} (case), {"source_type": "template"}, or {"source_type": ""}. Only the two exact literals pass.
Common situations: Adding a new source kind (e.g. 'workflow') without updating models; case drift between producer and consumer; empty string from a form instead of omitting the key.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- unknown delivery status {raw_status!r}
- 'failure_kind' must be 'dispatch', 'schedule', or null
- source.kind must be 'prompt' or 'playbook'
- delivery.mode must be 'in_app', 'origin', or 'configured'
- invalid risk_tier: {req.risk_tier}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/5ca57af7253fbce2.
Report an issue: GitHub.