HKUDS/Vibe-Trading · error · ValueError

invalid portfolio connection id: {connection_id or '?'}

Error message

invalid portfolio connection id: {connection_id or '?'}

What it means

A source's connection id (from 'connection_id' or legacy 'id', stripped and lowercased) must fully match _SOURCE_ID_RE; otherwise parsing aborts with the offending id (or '?' if empty). The regex enforces a safe slug charset for ids.

Source

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

    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(
                f"connection is not eligible for read-only portfolios: {connection_id}"
            )
        if connection_id in seen_ids:
            raise ValueError("portfolio connection ids must be unique")
        label = str(raw.get("label") or connection.label).strip()

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Use a simple lowercase slug: letters, digits, hyphens/underscores, e.g. 'binance-main'
  2. Ensure each entry has a non-empty connection_id (or id) key
  3. Check _SOURCE_ID_RE in agent/src/portfolio/config.py for the exact charset/length rules

Example fix

# before
{"connection_id": "Binance Main!!"}
# after
{"connection_id": "binance-main"}
Defensive patterns

Strategy: validation

Validate before calling

import re
_SOURCE_ID_RE = re.compile(r'^[a-z0-9][a-z0-9_-]*$')  # mirror the library's pattern
cid = str(entry.get('connection_id') or entry.get('id') or '').strip().lower()
if not _SOURCE_ID_RE.fullmatch(cid):
    entry['connection_id'] = slugify(cid)

Type guard

def is_valid_source_id(cid) -> bool:
    return isinstance(cid, str) and bool(re.fullmatch(r'[a-z0-9][a-z0-9_-]*', cid.strip().lower()))

Try / catch

try:
    settings = parse_settings(payload, store)
except ValueError as exc:
    if 'invalid portfolio connection id' in str(exc):
        for e in payload['sources']:
            e['connection_id'] = slugify(e.get('connection_id') or e.get('id') or '')
        settings = parse_settings(payload, store)

Prevention

When it happens

Trigger: Ids containing uppercase (lowered first), spaces, slashes, special characters, or an entirely missing connection_id/id key; also ids exceeding whatever length/charset _SOURCE_ID_RE mandates.

Common situations: Copy-pasting exchange account URIs or emails as ids, omitting connection_id and only providing profile_id, or trailing whitespace/punctuation from manual editing.

Related errors


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