HKUDS/Vibe-Trading · error · ValueError
sources must be a list
Error message
sources must be a list
What it means
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.
Source
Thrown at agent/src/portfolio/config.py:152
payload: Raw settings dict from the Web UI, CLI or a settings file.
connection_store: Local connection registry; a default store is used
when omitted.
Returns:
Validated, order-normalized settings.
Raises:
ValueError: If the display currency, the source list, a connection id,
a label, or a referenced profile's eligibility is invalid.
"""
store = connection_store or ConnectionStore()
currency = str(payload.get("display_currency") or "USD").strip().upper()
if currency not in {"USD", "CNY"}:
raise ValueError("display_currency must be USD or CNY")
raw_sources = payload.get("sources")
if not isinstance(raw_sources, list):
raise ValueError("sources must be a list")
if len(raw_sources) > 50:
raise ValueError("at most 50 portfolio sources are allowed")
seen_ids: set[str] = set()
sources: list[PortfolioSource] = []
for index, raw in enumerate(raw_sources):
if not isinstance(raw, dict):
raise ValueError("each portfolio source must be an object")
connection_id = (
str(raw.get("connection_id") or raw.get("id") or "").strip().lower()
)
if not _SOURCE_ID_RE.fullmatch(connection_id):
raise ValueError(f"invalid portfolio connection id: {connection_id or '?'}")
legacy_profile_id = str(raw.get("profile_id") or "").strip().lower()
if legacy_profile_id:
legacy_profile = profile_by_id(legacy_profile_id)
connection = store.ensure(
connection_id,View on GitHub (pinned to 80ffdda44c)
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()
Example fix
# before
{"sources": {"conn1": {"label": "Main"}}}
# after
{"sources": [{"connection_id": "conn1", "label": "Main"}]} Defensive patterns
Strategy: type-guard
Validate before calling
sources = payload.get('sources')
if not isinstance(sources, list):
if isinstance(sources, dict):
payload['sources'] = [{'connection_id': k, **v} for k, v in sources.items()]
else:
payload['sources'] = [] Type guard
def has_sources_list(payload) -> bool:
return isinstance(payload.get('sources'), list) Try / catch
try:
settings = parse_settings(payload, store)
except ValueError as exc:
if 'sources must be a list' in str(exc):
payload['sources'] = normalize_sources(payload['sources'])
settings = parse_settings(payload, store) Prevention
- 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
When it happens
Trigger: parse_settings({'sources': {...}}), {'sources': null}, or omitting 'sources' entirely; loading a settings file where sources was reshaped into an object keyed by connection id.
Common situations: 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.
Related errors
- each portfolio source must be an object
- agent config 'channels' must be an object
- agent config 'channels.feishu' must be an object
- hypotheses storage must contain a JSON list
- invalid portfolio settings: {exc}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/3659f77ed01f002b.
Report an issue: GitHub.