{"record":{"id":"69e97c9a870945e4","repo":"HKUDS/Vibe-Trading","slug":"connection-is-not-eligible-for-read-only-portfolio","errorCode":null,"errorMessage":"connection is not eligible for read-only portfolios: {connection_id}","messagePattern":"connection is not eligible for read-only portfolios: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/portfolio/config.py","lineNumber":178,"sourceCode":"            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(\n                f\"connection is not eligible for read-only portfolios: {connection_id}\"\n            )\n        if connection_id in seen_ids:\n            raise ValueError(\"portfolio connection ids must be unique\")\n        label = str(raw.get(\"label\") or connection.label).strip()\n        if (\n            not label\n            or len(label) > 80\n            or any(ord(character) < 32 for character in label)\n        ):\n            raise ValueError(\n                \"portfolio source labels must contain 1 to 80 printable characters\"\n            )\n        seen_ids.add(connection_id)\n        sources.append(\n            PortfolioSource(\n                connection_id=connection_id,\n                label=label,","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/portfolio/config.py#L160-L196","documentation":"Every portfolio source connection must resolve to a profile that is in eligible_profiles() — i.e. profiles permitted to back read-only portfolio data. Using a connection tied to a trade-oriented or otherwise ineligible profile raises this error.","triggerScenarios":"Referencing a connection whose profile_id maps to a trade profile; a legacy profile_id entry whose migrated profile is discovery-only or trade; tests asserting that discovery-only profiles cannot back a source hit exactly this branch.","commonSituations":"Migrating old configs where profile_id connections were auto-mapped, enabling a trade-capable API key for portfolio reads, or profile eligibility changing between releases.","solutions":["Use a connection created from a read-only eligible profile (see eligible_profiles())","Re-key the API connection with view-only permissions and recreate the connection","Update the profile definition if it should be eligible, keeping security review in mind"],"exampleFix":"# before\n{\"connection_id\": \"binance-main\", \"profile_id\": \"full-trade\"}\n# after\n{\"connection_id\": \"binance-readonly\", \"label\": \"Binance (view only)\"}","handlingStrategy":"validation","validationCode":"from agent.src.profiles import eligible_profiles\nconn = store.get(cid)\neligible = {p.id for p in eligible_profiles()}\nif conn.profile_id not in eligible:\n    raise PermissionError(f'{cid} uses an ineligible profile; pick a read-only profile')","typeGuard":"def connection_is_eligible(store, cid) -> bool:\n    return store.get(cid).profile_id in {p.id for p in eligible_profiles()}","tryCatchPattern":"try:\n    settings = parse_settings(payload, store)\nexcept ValueError as exc:\n    if 'not eligible' in str(exc):\n        drop_ineligible_sources(payload)\n        settings = parse_settings(payload, store)  # degraded, warn user","preventionTips":["Create dedicated read-only connections for portfolio sources","Check eligible_profiles() when onboarding a connection for portfolio use","Re-validate configs after profile eligibility rules change on upgrade"],"tags":["python","config","authorization","profiles"],"backgroundTag":"permission-denied-by-policy","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}