{"record":{"id":"3659f77ed01f002b","repo":"HKUDS/Vibe-Trading","slug":"sources-must-be-a-list","errorCode":null,"errorMessage":"sources must be a list","messagePattern":"sources must be a list","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/portfolio/config.py","lineNumber":152,"sourceCode":"        payload: Raw settings dict from the Web UI, CLI or a settings file.\n        connection_store: Local connection registry; a default store is used\n            when omitted.\n\n    Returns:\n        Validated, order-normalized settings.\n\n    Raises:\n        ValueError: If the display currency, the source list, a connection id,\n            a label, or a referenced profile's eligibility is invalid.\n    \"\"\"\n    store = connection_store or ConnectionStore()\n    currency = str(payload.get(\"display_currency\") or \"USD\").strip().upper()\n    if currency not in {\"USD\", \"CNY\"}:\n        raise ValueError(\"display_currency must be USD or CNY\")\n\n    raw_sources = payload.get(\"sources\")\n    if not isinstance(raw_sources, list):\n        raise ValueError(\"sources must be a list\")\n    if len(raw_sources) > 50:\n        raise ValueError(\"at most 50 portfolio sources are allowed\")\n\n    seen_ids: set[str] = set()\n    sources: list[PortfolioSource] = []\n    for index, raw in enumerate(raw_sources):\n        if not isinstance(raw, dict):\n            raise ValueError(\"each portfolio source must be an object\")\n        connection_id = (\n            str(raw.get(\"connection_id\") or raw.get(\"id\") or \"\").strip().lower()\n        )\n        if not _SOURCE_ID_RE.fullmatch(connection_id):\n            raise ValueError(f\"invalid portfolio connection id: {connection_id or '?'}\")\n        legacy_profile_id = str(raw.get(\"profile_id\") or \"\").strip().lower()\n        if legacy_profile_id:\n            legacy_profile = profile_by_id(legacy_profile_id)\n            connection = store.ensure(\n                connection_id,","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/portfolio/config.py#L134-L170","documentation":"Portfolio settings require the 'sources' key to be a JSON array; any other type (object, string, null, number) is rejected at parse time before per-source validation runs. An absent sources key yields None and also fails.","triggerScenarios":"parse_settings({'sources': {...}}), {'sources': null}, or omitting 'sources' entirely; loading a settings file where sources was reshaped into an object keyed by connection id.","commonSituations":"Hand-editing or generating the settings file with the wrong shape, schema drift after a config format migration, or YAML-to-JSON conversion quirks that turn a list into a map.","solutions":["Make sources a JSON list of source objects: \"sources\": [ {...}, ... ]","If empty, use \"sources\": [] rather than removing the key or using {}","After external edits, validate shape with json before calling load()"],"exampleFix":"# before\n{\"sources\": {\"conn1\": {\"label\": \"Main\"}}}\n# after\n{\"sources\": [{\"connection_id\": \"conn1\", \"label\": \"Main\"}]}","handlingStrategy":"type-guard","validationCode":"sources = payload.get('sources')\nif not isinstance(sources, list):\n    if isinstance(sources, dict):\n        payload['sources'] = [{'connection_id': k, **v} for k, v in sources.items()]\n    else:\n        payload['sources'] = []","typeGuard":"def has_sources_list(payload) -> bool:\n    return isinstance(payload.get('sources'), list)","tryCatchPattern":"try:\n    settings = parse_settings(payload, store)\nexcept ValueError as exc:\n    if 'sources must be a list' in str(exc):\n        payload['sources'] = normalize_sources(payload['sources'])\n        settings = parse_settings(payload, store)","preventionTips":["Always emit sources as a JSON array in generators/templates","Use [] for an empty source list instead of removing the key","Schema-validate settings files after external edits"],"tags":["python","config","json","schema"],"backgroundTag":"wrong-config-type","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}