{"record":{"id":"b5b940fc7f1ca498","repo":"HKUDS/Vibe-Trading","slug":"each-portfolio-source-must-be-an-object","errorCode":null,"errorMessage":"each portfolio source must be an object","messagePattern":"each portfolio source must be an object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/portfolio/config.py","lineNumber":160,"sourceCode":"        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,\n                legacy_profile.id,\n                str(raw.get(\"label\") or legacy_profile.label),\n            )\n        else:\n            connection = store.get(connection_id)\n        profile = profile_by_id(connection.profile_id)\n        if profile not in eligible_profiles():\n            raise ValueError(","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/portfolio/config.py#L142-L178","documentation":"Each entry in the portfolio settings 'sources' array must be a JSON object (dict); scalars, strings, arrays, or nulls are rejected during per-entry validation at the given index.","triggerScenarios":"\"sources\": [\"conn1\", {...}] or [{null}] — e.g. a list of ids instead of objects, or a trailing null from list-building code.","commonSituations":"Writing shorthand configs (list of ids), LLM/manual generation of the file, or JSON round-trips that flatten entries.","solutions":["Wrap every entry as an object: {\"connection_id\": \"...\", ...}","Validate the parsed JSON shape before saving/loading the file","Remove stray null/undefined entries from the array"],"exampleFix":"# before\n\"sources\": [\"binance-main\"]\n# after\n\"sources\": [{\"connection_id\": \"binance-main\"}]","handlingStrategy":"type-guard","validationCode":"payload['sources'] = [s for s in payload['sources'] if isinstance(s, dict)]\nif len(payload['sources']) != original_len:\n    log.warning('dropped malformed source entries')","typeGuard":"def all_sources_are_objects(payload) -> bool:\n    return all(isinstance(s, dict) for s in payload.get('sources', []))","tryCatchPattern":"try:\n    settings = parse_settings(payload, store)\nexcept ValueError as exc:\n    if 'must be an object' in str(exc):\n        payload['sources'] = [normalize_entry(s) for s in payload['sources'] if s]\n        settings = parse_settings(payload, store)","preventionTips":["Don't write shorthand id lists; always use entry objects","Validate generator output with a JSON schema","Filter null entries produced by list-building code"],"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"}