{"record":{"id":"cae88f7fb4faabe3","repo":"github/copilot-sdk","slug":"tool-filter-must-be-a-toolset-or-list-str-not-st","errorCode":null,"errorMessage":"tool filter must be a ToolSet or list[str], not str. Pass a single-element list (e.g. [\"builtin:bash\"]) or a ToolSet (e.g. ToolSet().add_builtin('bash')).","messagePattern":"tool filter must be a ToolSet or list\\[str\\], not str\\. Pass a single-element list \\(e\\.g\\. \\[\"builtin:bash\"\\]\\) or a ToolSet \\(e\\.g\\. ToolSet\\(\\)\\.add_builtin\\('bash'\\)\\)\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"python/copilot/_mode.py","lineNumber":121,"sourceCode":"    \"list_agents\",\n    \"send_inbox\",\n    \"context_board\",\n    \"skill\",\n]\n\n\ndef _normalize_tool_filter(value: Any) -> list[str] | None:\n    \"\"\"Accept ``ToolSet``, ``list[str]``, or ``None``; return a list or ``None``.\n\n    Reject plain ``str`` explicitly — ``list(\"foo\")`` would silently shred it\n    into characters, sending an invalid tool filter list on the wire.\n    \"\"\"\n    if value is None:\n        return None\n    if isinstance(value, ToolSet):\n        return value.to_list()\n    if isinstance(value, str):\n        raise TypeError(\n            \"tool filter must be a ToolSet or list[str], not str. \"\n            'Pass a single-element list (e.g. [\"builtin:bash\"]) or a '\n            \"ToolSet (e.g. ToolSet().add_builtin('bash')).\"\n        )\n    return list(value)\n\n\ndef _validate_tool_filter_list(field: str, items: list[str] | None) -> None:\n    \"\"\"Reject bare ``\"*\"`` entries (must use ``builtin:*``/``mcp:*``/``custom:*``).\"\"\"\n    if items is None:\n        return\n    for entry in items:\n        if entry == \"*\":\n            raise ValueError(\n                f\"invalid {field} entry '*': there is no bare wildcard. \"\n                \"Use ToolSet().add_builtin('*'), .add_mcp('*'), or \"\n                \".add_custom('*') to target a specific source.\"\n            )","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/python/copilot/_mode.py#L103-L139","documentation":"The tool filter for create_session/resume_session must be a ToolSet or a list of strings — a bare string is rejected with guidance on the correct forms. This is an explicit anti-footgun: a single string like 'builtin:bash' would otherwise be iterated character-by-character into a meaningless list of filters.","triggerScenarios":"Calling create_session(tools='builtin:bash') or resume_session(available_tools=\"mcp:*\") — passing a str instead of ['builtin:bash'] or ToolSet().add_builtin('bash').","commonSituations":"Developers pass a single tool name directly because one tool is all they need, or copy an example that used a list but pass a string built from a variable; older code that predates the ToolSet API may still pass strings.","solutions":["Wrap the string in a list: tools=['builtin:bash']","Prefer the typed API: ToolSet().add_builtin('bash') passed as the filter","Update legacy call sites that passed a single tool name string to use the list/ToolSet form","Coerce defensively in shared helpers: tools = [tools] if isinstance(tools, str) else tools"],"exampleFix":"// before\nclient.create_session(tools=\"builtin:bash\")\n\n// after\nclient.create_session(tools=[\"builtin:bash\"])\n# or\nclient.create_session(tools=ToolSet().add_builtin(\"bash\"))","handlingStrategy":"type-guard","validationCode":"if isinstance(tools, str):\n    raise TypeError(\"pass a list[str] or ToolSet, not a bare string\")","typeGuard":"def is_valid_tool_filter(value) -> bool:\n    return value is None or isinstance(value, (ToolSet, list))","tryCatchPattern":"try:\n    session = client.create_session(tools=tools)\nexcept TypeError as e:\n    if \"not str\" in str(e):\n        session = client.create_session(tools=[tools])","preventionTips":["Always build filters with ToolSet rather than raw strings/lists","Wrap single tool names in a list immediately: [name]","Enable type checking so str passed where list[str] is expected is flagged","Centralize session creation in one helper that normalizes the filter argument"],"tags":["type-mismatch","python","tools","api-usage"],"backgroundTag":"type-mismatch","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}