{"record":{"id":"6b0c41225f08a395","repo":"HKUDS/Vibe-Trading","slug":"name-must-be-a-string-or-null","errorCode":null,"errorMessage":"'{name}' must be a string or null","messagePattern":"'(.+?)' must be a string or null","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"agent/src/scheduled_research/models.py","lineNumber":498,"sourceCode":"            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\")\n        return cls(\n            id=job_id,\n            prompt=prompt,\n            schedule=schedule,\n            title=title,\n            source_type=source_type,\n            playbook_slug=playbook_slug,\n            end_at=end_at,\n            next_run_at=next_run_at,\n            status=status,\n            created_at=created_at,\n            last_run_at=last_run_at,\n            consecutive_failures=consecutive_failures,\n            last_error=last_error,\n            failure_kind=failure_kind,\n            config=config,\n            timezone=tz,\n            delivery_channel=delivery_channel,","sourceCodeStart":480,"sourceCodeEnd":516,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/models.py#L480-L516","documentation":"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.","triggerScenarios":"{\"delivery_channel\": \"slack\"} is fine, but {\"delivery_channel\": [\"slack\"]}, {\"delivery_target\": 12345}, or a dict in delivery_target_label raises.","commonSituations":"Structured delivery configs (channel objects) flattened incorrectly; numeric chat/channel IDs from provider APIs stored uncast; schema drift when adding the ref/label fields.","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"],"exampleFix":"// before\nrecord[\"delivery_target\"] = channel[\"id\"]  # may be int\n\n// after\nrecord[\"delivery_target\"] = str(channel[\"id\"])","handlingStrategy":"validation","validationCode":"ROUTING = (\"delivery_channel\", \"delivery_target\", \"delivery_target_ref\", \"delivery_target_label\")\n\ndef routing_ok(d):\n    return all(d.get(k) is None or isinstance(d.get(k), str) for k in ROUTING)","typeGuard":"ROUTING = (\"delivery_channel\", \"delivery_target\", \"delivery_target_ref\", \"delivery_target_label\")\n\ndef sanitized_routing(data):\n    out = dict(data)\n    for k in ROUTING:\n        v = out.get(k)\n        if v is not None and not isinstance(v, str):\n            out[k] = str(v)\n    return out","tryCatchPattern":"try:\n    job = Job.from_dict(record)\nexcept TypeError as exc:\n    if \"must be a string or null\" in str(exc):\n        record = dict(record)\n        for k in (\"delivery_channel\", \"delivery_target\", \"delivery_target_ref\", \"delivery_target_label\"):\n            if record.get(k) is not None and not isinstance(record[k], str):\n                record[k] = str(record[k])\n        job = Job.from_dict(record)\n    else:\n        raise","preventionTips":["Cast provider IDs (Slack channel, email address objects) to str early","Keep routing config as four scalar strings","Build records via constructors/to_dict"],"tags":["deserialization","type-error","delivery-routing","scheduled-research"],"backgroundTag":"json-field-type-mismatch","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}