{"record":{"id":"d3ba790ee457be36","repo":"OpenBMB/ChatDev","slug":"tool-sources-must-be-a-list-of-strings","errorCode":null,"errorMessage":"tool_sources must be a list of strings","messagePattern":"tool_sources must be a list of strings","errorType":"validation","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"entity/configs/node/tooling.py","lineNumber":396,"sourceCode":"        else:\n            raise ConfigError(\"timeout must be numeric\", extend_path(path, \"timeout\"))\n\n        cache_ttl_value = mapping.get(\"cache_ttl\", 0.0)\n        if cache_ttl_value is None:\n            cache_ttl = 0.0\n        elif isinstance(cache_ttl_value, (int, float)):\n            cache_ttl = float(cache_ttl_value)\n        else:\n            raise ConfigError(\"cache_ttl must be numeric\", extend_path(path, \"cache_ttl\"))\n\n        tool_sources_raw = mapping.get(\"tool_sources\")\n        tool_sources: List[str] | None = None\n        if tool_sources_raw is not None:\n            entries = ensure_list(tool_sources_raw)\n            normalized: List[str] = []\n            for idx, entry in enumerate(entries):\n                if not isinstance(entry, str):\n                    raise ConfigError(\n                        \"tool_sources must be a list of strings\",\n                        extend_path(path, f\"tool_sources[{idx}]\"),\n                    )\n                value = entry.strip()\n                if value:\n                    normalized.append(value)\n            tool_sources = normalized\n        else:\n            tool_sources = [\"mcp_tools\"]\n\n        return cls(\n            server=server,\n            headers=headers,\n            timeout=timeout,\n            cache_ttl=cache_ttl,\n            tool_sources=tool_sources,\n            path=path,\n        )","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/OpenBMB/ChatDev/blob/4fb2db0ea90375ce1059f44fe03ffbd191a7a169/entity/configs/node/tooling.py#L378-L414","documentation":"Raised when tooling 'tool_sources' is a list containing a non-string entry. Each entry is validated item-by-item and the ConfigError path points at the exact offending index (tool_sources[N]).","triggerScenarios":"tool_sources: [\"web\", 3] or [\"web\", null] in the config mapping; a YAML list mixing scalars; a list of dicts describing tools.","commonSituations":"Refactoring tool source names from objects to plain strings and leaving one dict behind; JSON merging that injects numbers; empty-string entries are silently dropped, so only type errors trigger this.","solutions":["Ensure every element of tool_sources is a plain string","Replace object entries like {\"name\": \"x\"} with \"x\"","Strip/convert values before passing the mapping, e.g. [str(s) for s in tool_sources] if all entries are scalar"],"exampleFix":"# before\ntool_sources:\n  - web\n  - {name: db}\n# after\ntool_sources:\n  - web\n  - db","handlingStrategy":"type-guard","validationCode":"ts = cfg.get(\"tool_sources\")\nif ts is not None:\n    assert isinstance(ts, list) and all(isinstance(x, str) for x in ts)","typeGuard":"def valid_tool_sources(v) -> bool:\n    return v is None or (isinstance(v, list) and all(isinstance(x, str) for x in v))","tryCatchPattern":"except ConfigError as e:\n    if \"tool_sources\" in str(e):\n        cfg[\"tool_sources\"] = [str(s) for s in cfg[\"tool_sources\"] if isinstance(s, (str, int))]","preventionTips":["Use flat string lists in configs","The error path names the exact bad index — use it to fix the single entry"],"tags":["config","validation","list","tooling"],"backgroundTag":"config-type-validation-failed","analyzedSha":"4fb2db0ea90375ce1059f44fe03ffbd191a7a169","analyzedAt":"2026-08-27T14:35:29.622Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}