{"record":{"id":"51a5dea80e487f42","repo":"HKUDS/Vibe-Trading","slug":"symbols-must-be-a-non-empty-list-of-strings","errorCode":null,"errorMessage":"symbols must be a non-empty list of strings","messagePattern":"symbols must be a non-empty list of strings","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/tools/portfolio_risk_tool.py","lineNumber":99,"sourceCode":"        # Injectable for tests; production uses the real fallback chain.\n        self._fetch = data_fetcher or fetch_market_data\n\n    def execute(self, **kwargs: Any) -> str:\n        try:\n            return self._run(**kwargs)\n        except Exception as exc:  # noqa: BLE001 — tool must always return JSON\n            logger.warning(\"portfolio_risk_xray failed: %s\", exc)\n            return json.dumps(\n                {\"status\": \"error\", \"error\": str(exc)}, ensure_ascii=False, allow_nan=False\n            )\n\n    # ------------------------------------------------------------------\n    def _run(self, **kwargs: Any) -> str:\n        symbols = kwargs.get(\"symbols\")\n        if not isinstance(symbols, list) or not symbols or not all(\n            isinstance(s, str) and s.strip() for s in symbols\n        ):\n            raise ValueError(\"symbols must be a non-empty list of strings\")\n        symbols = [s.strip() for s in symbols]\n        if len(symbols) > _MAX_SYMBOLS:\n            raise ValueError(f\"too many symbols ({len(symbols)}); cap is {_MAX_SYMBOLS}\")\n\n        weights = self._parse_weights(kwargs.get(\"weights\"), symbols)\n        start_date, end_date = self._parse_dates(kwargs.get(\"start_date\"), kwargs.get(\"end_date\"))\n        source = str(kwargs.get(\"source\") or \"auto\")\n        interval = str(kwargs.get(\"interval\") or \"1D\")\n\n        raw = self._fetch(\n            codes=symbols,\n            start_date=start_date,\n            end_date=end_date,\n            source=source,\n            interval=interval,\n        )\n        closes = self._closes_frame(raw, symbols)\n        unresolved = raw.get(\"_unresolved\") if isinstance(raw, Mapping) else None","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/tools/portfolio_risk_tool.py#L81-L117","documentation":"PortfolioRiskTool._run requires 'symbols' to be a non-empty JSON array whose entries are all non-blank strings; anything else (missing key, string, empty list, non-string or whitespace-only entries) raises this before any downstream processing.","triggerScenarios":"symbols='AAPL,MSFT' (string not list), symbols=[], symbols=['AAPL', ''], or symbols=['AAPL', 5].","commonSituations":"LLM serializing a comma-joined string, splitting that yields empty tokens, or reusing a ticker dict instead of list.","solutions":["Pass a list of trimmed tickers: ['AAPL', 'MSFT']","Split-and-filter string input before calling","Cap length to _MAX_SYMBOLS to avoid the next error"],"exampleFix":"# before\nexecute(symbols=\"AAPL,MSFT\")\n# after\nexecute(symbols=[s.strip() for s in \"AAPL,MSFT\".split(\",\") if s.strip()])","handlingStrategy":"type-guard","validationCode":"symbols = symbols if isinstance(symbols, list) else (\n    [s.strip() for s in str(symbols).split(\",\") if s.strip()] if symbols else []\n)\nif not symbols:\n    raise ArgumentError(\"no symbols\")","typeGuard":"def is_symbol_list(v: object) -> bool:\n    return (\n        isinstance(v, list) and bool(v)\n        and all(isinstance(s, str) and s.strip() for s in v)\n    )","tryCatchPattern":"try:\n    out = tool.execute(symbols=symbols)\nexcept ValueError as e:\n    if \"non-empty list of strings\" in str(e):\n        out = tool.execute(symbols=coerce_symbol_list(symbols))","preventionTips":["Always send JSON arrays of tickers","Trim and drop empty tokens when splitting","Keep tickers as strings, never numeric tickers"],"tags":["portfolio-risk","input-validation","symbols"],"backgroundTag":"argument-type-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}