HKUDS/Vibe-Trading · error · ValueError

each portfolio source must be an object

Error message

each portfolio source must be an object

What it means

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.

Source

Thrown at agent/src/portfolio/config.py:160

        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,
                legacy_profile.id,
                str(raw.get("label") or legacy_profile.label),
            )
        else:
            connection = store.get(connection_id)
        profile = profile_by_id(connection.profile_id)
        if profile not in eligible_profiles():
            raise ValueError(

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Wrap every entry as an object: {"connection_id": "...", ...}
  2. Validate the parsed JSON shape before saving/loading the file
  3. Remove stray null/undefined entries from the array

Example fix

# before
"sources": ["binance-main"]
# after
"sources": [{"connection_id": "binance-main"}]
Defensive patterns

Strategy: type-guard

Validate before calling

payload['sources'] = [s for s in payload['sources'] if isinstance(s, dict)]
if len(payload['sources']) != original_len:
    log.warning('dropped malformed source entries')

Type guard

def all_sources_are_objects(payload) -> bool:
    return all(isinstance(s, dict) for s in payload.get('sources', []))

Try / catch

try:
    settings = parse_settings(payload, store)
except ValueError as exc:
    if 'must be an object' in str(exc):
        payload['sources'] = [normalize_entry(s) for s in payload['sources'] if s]
        settings = parse_settings(payload, store)

Prevention

When it happens

Trigger: "sources": ["conn1", {...}] or [{null}] — e.g. a list of ids instead of objects, or a trailing null from list-building code.

Common situations: Writing shorthand configs (list of ids), LLM/manual generation of the file, or JSON round-trips that flatten entries.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/b5b940fc7f1ca498. Report an issue: GitHub.