{"record":{"id":"94c31384e550e14d","repo":"HKUDS/Vibe-Trading","slug":"source-variables-must-be-an-object","errorCode":null,"errorMessage":"source.variables must be an object","messagePattern":"source\\.variables must be an object","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"agent/src/scheduled_research/service.py","lineNumber":146,"sourceCode":"        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\n    else:\n        raise ValueError(\"source.kind must be 'prompt' or 'playbook'\")\n\n    expression = str(schedule_spec.get(\"expression\") or \"\").strip()\n    timezone = schedule_spec.get(\"timezone\")\n    validate_schedule(expression)\n    if is_interval_schedule(expression):\n        validate_timezone_shape(timezone)\n    else:\n        validate_timezone(timezone)\n    next_run_at = now\n    if timezone is not None and not is_interval_schedule(expression):\n        next_run_at = next_due(expression, now, timezone)\n\n    end_at = _parse_end_at(draft.get(\"end_at\"))\n    if end_at is not None and end_at <= now:","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/service.py#L128-L164","documentation":"For playbook sources, source.variables (used by playbook.render) must be a JSON object/Mapping when provided. Arrays, strings, or scalars are rejected because template variables are keyed.","triggerScenarios":"Sending variables as a list of pairs, a JSON-encoded string, or null-adjacent scalars (note: null/absent defaults to {}).","commonSituations":"Double-serialized JSON (variables: '{\"k\":1}'); clients mapping form arrays into the field; API glue code passing **kwargs lists.","solutions":["Send variables as an object: {\"topic\": \"AI\", \"depth\": 3}","Omit it or pass null to use no variables","If the string case is double-encoding, JSON.parse it client-side before submit"],"exampleFix":"// before\n{\"variables\": \"{\\\"topic\\\": \\\"AI\\\"}\"}\n// after\n{\"variables\": {\"topic\": \"AI\"}}","handlingStrategy":"type-guard","validationCode":"from collections.abc import Mapping\nv = draft.get(\"source\", {}).get(\"variables\")\nassert v is None or isinstance(v, Mapping)","typeGuard":"from collections.abc import Mapping\n\ndef variables_valid(src: dict) -> bool:\n    v = src.get(\"variables\")\n    return v is None or isinstance(v, Mapping)","tryCatchPattern":"try:\n    propose_create(draft)\nexcept ValueError as e:\n    if \"variables must be an object\" in str(e):\n        draft[\"source\"][\"variables\"] = json.loads(draft[\"source\"][\"variables\"]) if isinstance(draft[\"source\"].get(\"variables\"), str) else {}","preventionTips":["Avoid double-JSON-encoding variables","Build variables as dicts in client code, not serialized strings"],"tags":["scheduled-research","validation","playbook","variables"],"backgroundTag":"field-type-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}