{"record":{"id":"595eae5016428ded","repo":"HKUDS/Vibe-Trading","slug":"delivery-must-be-an-object","errorCode":null,"errorMessage":"delivery must be an object","messagePattern":"delivery must be an object","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"agent/src/scheduled_research/service.py","lineNumber":130,"sourceCode":"\ndef build_job_from_draft(\n    draft: Mapping[str, Any],\n    *,\n    session_id: str | None = None,\n    now_ms: int | None = None,\n) -> ScheduledResearchJob:\n    \"\"\"Validate the public draft and build a persisted-model job.\"\"\"\n    now = int(time.time() * 1000) if now_ms is None else now_ms\n    title = str(draft.get(\"title\") or \"\").strip()\n    if not title:\n        raise ValueError(\"title is required\")\n    source = draft.get(\"source\")\n    schedule_spec = draft.get(\"schedule\")\n    delivery_spec = draft.get(\"delivery\") or {\"mode\": \"in_app\"}\n    if not isinstance(source, Mapping) or not isinstance(schedule_spec, Mapping):\n        raise ValueError(\"source and schedule must be objects\")\n    if not isinstance(delivery_spec, Mapping):\n        raise ValueError(\"delivery must be an object\")\n\n    source_type = str(source.get(\"kind\") or \"prompt\")\n    playbook_slug = None\n    config: dict[str, Any] = {}\n    if source_type == \"prompt\":\n        prompt = str(source.get(\"prompt\") or \"\").strip()\n        if not prompt:\n            raise ValueError(\"source.prompt is required\")\n    elif source_type == \"playbook\":\n        playbook_slug = str(source.get(\"playbook_slug\") or \"\").strip()\n        if not playbook_slug:\n            raise ValueError(\"source.playbook_slug is required\")\n        playbook = get_playbook(playbook_slug)\n        variables = source.get(\"variables\") or {}\n        if not isinstance(variables, Mapping):\n            raise ValueError(\"source.variables must be an object\")\n        prompt = playbook.render(variables)\n        config[\"playbook\"] = playbook_slug","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/service.py#L112-L148","documentation":"The draft's optional 'delivery' field, when present and non-null, must be a JSON object. A scalar, list, or non-null non-object value triggers this error (null/absent defaults to {'mode':'in_app'}).","triggerScenarios":"Sending delivery: \"telegram\" or delivery: [\"origin\"] instead of {\"mode\": ...}; frontend serializing the wrong key shape.","commonSituations":"Refactors that flattened delivery to a mode string; version drift where older clients sent a string mode; typos in payload construction.","solutions":["Use an object: {\"mode\": \"in_app\"} / {\"mode\": \"origin\"} / {\"mode\": \"configured\", \"target_ref\": ...}","Omit or null the field to accept the in_app default","Validate the draft payload shape client-side"],"exampleFix":"// before\n{\"delivery\": \"origin\"}\n// after\n{\"delivery\": {\"mode\": \"origin\"}}","handlingStrategy":"validation","validationCode":"from collections.abc import Mapping\nd = draft.get(\"delivery\")\nassert d is None or isinstance(d, Mapping)","typeGuard":"from collections.abc import Mapping\n\ndef delivery_valid(d) -> bool:\n    return d is None or (isinstance(d, Mapping) and not isinstance(d, (str, list)))","tryCatchPattern":"try:\n    build_job_from_draft(draft)\nexcept ValueError as e:\n    if \"delivery must be an object\" in str(e):\n        draft[\"delivery\"] = {\"mode\": draft[\"delivery\"]} if isinstance(draft[\"delivery\"], str) else {\"mode\": \"in_app\"}","preventionTips":["Never send delivery as a bare mode string","Serialize from a typed DeliverySpec object"],"tags":["scheduled-research","validation","delivery","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}