{"record":{"id":"5ca57af7253fbce2","repo":"HKUDS/Vibe-Trading","slug":"source-type-must-be-prompt-or-playbook","errorCode":null,"errorMessage":"'source_type' must be 'prompt' or 'playbook'","messagePattern":"'source_type' must be 'prompt' or 'playbook'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/scheduled_research/models.py","lineNumber":480,"sourceCode":"        # non-string values all normalize to None.\n        raw_tz = data.get(\"timezone\")\n        tz = raw_tz if isinstance(raw_tz, str) and raw_tz.strip() else None\n        if raw_tz is not None and tz is None:\n            logger.warning(\n                \"scheduled research job %s has an unusable timezone %r; \"\n                \"evaluating its schedule in UTC\",\n                job_id,\n                raw_tz,\n            )\n        status = JobStatus(data[\"status\"])\n        title = data.get(\"title\", \"\")\n        source_type = data.get(\"source_type\", \"prompt\")\n        playbook_slug = data.get(\"playbook_slug\")\n        end_at = data.get(\"end_at\")\n        if not isinstance(title, str):\n            raise TypeError(\"'title' must be a string\")\n        if source_type not in {\"prompt\", \"playbook\"}:\n            raise ValueError(\"'source_type' must be 'prompt' or 'playbook'\")\n        if playbook_slug is not None and not isinstance(playbook_slug, str):\n            raise TypeError(\"'playbook_slug' must be a string or null\")\n        if end_at is not None and (isinstance(end_at, bool) or not isinstance(end_at, int)):\n            raise TypeError(\"'end_at' must be an integer (epoch ms) or null\")\n        raw_config = data.get(\"config\")\n        config: Dict[str, Any] = raw_config if isinstance(raw_config, dict) else {}\n        delivery_channel = data.get(\"delivery_channel\")\n        delivery_target = data.get(\"delivery_target\")\n        delivery_target_ref = data.get(\"delivery_target_ref\")\n        delivery_target_label = data.get(\"delivery_target_label\")\n        for name, value in (\n            (\"delivery_channel\", delivery_channel),\n            (\"delivery_target\", delivery_target),\n            (\"delivery_target_ref\", delivery_target_ref),\n            (\"delivery_target_label\", delivery_target_label),\n        ):\n            if value is not None and not isinstance(value, str):\n                raise TypeError(f\"'{name}' must be a string or null\")","sourceCodeStart":462,"sourceCodeEnd":498,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/models.py#L462-L498","documentation":"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.","triggerScenarios":"{\"source_type\": \"Playbook\"} (case), {\"source_type\": \"template\"}, or {\"source_type\": \"\"}. Only the two exact literals pass.","commonSituations":"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.","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()"],"exampleFix":"// before\nrecord[\"source_type\"] = \"Playbook\"\n\n// after\nrecord[\"source_type\"] = \"playbook\"","handlingStrategy":"validation","validationCode":"SOURCE_TYPES = {\"prompt\", \"playbook\"}\n\ndef source_type_ok(v):\n    return v in SOURCE_TYPES  # note: absent defaults to 'prompt'","typeGuard":"SOURCE_TYPES = {\"prompt\", \"playbook\"}\n\ndef safe_source_type(v):\n    if isinstance(v, str) and v.lower() in SOURCE_TYPES:\n        return v.lower()\n    return \"prompt\"","tryCatchPattern":"try:\n    job = Job.from_dict(record)\nexcept ValueError as exc:\n    if \"source_type\" in str(exc):\n        record = dict(record); record[\"source_type\"] = \"prompt\"\n        job = Job.from_dict(record)\n    else:\n        raise","preventionTips":["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"],"tags":["deserialization","validation","enum","scheduled-research"],"backgroundTag":"invalid-enum-value","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}