{"record":{"id":"ae9b62f858cc605d","repo":"github/copilot-sdk","slug":"invalid-kind-tool-name-name-r-tool-names-must","errorCode":null,"errorMessage":"invalid {kind} tool name {name!r}: tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard '*'","messagePattern":"invalid (.+?) tool name (.+?): tool names must match /\\^\\[a-zA-Z0-9_-\\]\\+\\$/ or be the wildcard '\\*'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/copilot/_mode.py","lineNumber":30,"sourceCode":"import re\nfrom collections.abc import Iterable\nfrom typing import TYPE_CHECKING, Any, Literal\n\nif TYPE_CHECKING:\n    from .session import MemoryConfiguration\n\nCopilotClientMode = Literal[\"copilot-cli\", \"empty\"]\n\n_TOOL_NAME_REGEX = re.compile(r\"^[a-zA-Z0-9_-]+$\")\n\n\ndef _validate_tool_name(kind: str, name: str) -> None:\n    if not name:\n        raise ValueError(f\"invalid {kind} tool name: must not be empty\")\n    if name == \"*\":\n        return\n    if not _TOOL_NAME_REGEX.match(name):\n        raise ValueError(\n            f\"invalid {kind} tool name {name!r}: tool names must match \"\n            r\"/^[a-zA-Z0-9_-]+$/ or be the wildcard '*'\"\n        )\n\n\nclass ToolSet:\n    \"\"\"Builder for source-qualified tool filter patterns.\n\n    ``ToolSet`` accumulates entries like ``builtin:bash``, ``mcp:*``, or\n    ``custom:my_tool`` for use in\n    :class:`CopilotClient.create_session`'s ``available_tools`` /\n    ``excluded_tools`` parameters.\n\n    Tool classification (``builtin``/``mcp``/``custom``) is determined by the\n    runtime at registration time — not by name parsing — so\n    ``add_builtin(\"foo\")`` only matches tools the runtime registered as\n    built-in.\n    \"\"\"","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/python/copilot/_mode.py#L12-L48","documentation":"Tool names must match the regex /^[a-zA-Z0-9_-]+$/ (alphanumerics, underscores, hyphens) or be exactly the wildcard \"*\". _validate_tool_name enforces this for builtin, custom, and MCP tool registrations so tool filters remain unambiguous and wire-compatible.","triggerScenarios":"Calling add_builtin/add_custom/add_mcp with a name containing spaces, colons, dots, slashes, or unicode characters — e.g. add_custom('my.tool'), add_mcp('server/tool'), or a name with a trailing newline from a config file.","commonSituations":"Developers try to register fully-qualified names like 'builtin:bash' inside a specific source (the prefix is implicit), or copy tool names with path/namespace separators from MCP server listings; whitespace from YAML/JSON config leaks into names.","solutions":["Strip source prefixes: pass 'bash' not 'builtin:bash' to add_builtin (the kind is chosen by the method called)","Sanitize the name: replace illegal characters with '-' or '_' and strip whitespace/newlines","Use add_mcp('*') wildcard syntax instead of embedding source qualification in the name","Validate names against ^[a-zA-Z0-9_-]+$ in your config loader before constructing the ToolSet"],"exampleFix":"// before\ntoolset.add_builtin('builtin:bash')  # colon not allowed\n\n// after\ntoolset.add_builtin('bash')  # or toolset.add_builtin('*') for all builtin tools","handlingStrategy":"validation","validationCode":"import re\nTOOL_NAME_RE = re.compile(r\"^[a-zA-Z0-9_-]+$\")\nif not (name == \"*\" or TOOL_NAME_RE.match(name)):\n    raise ValueError(f\"invalid tool name {name!r}\")","typeGuard":"def is_valid_tool_name(name) -> bool:\n    return isinstance(name, str) and (name == \"*\" or bool(re.match(r\"^[a-zA-Z0-9_-]+$\", name)))","tryCatchPattern":"try:\n    toolset.add_mcp(server_name)\nexcept ValueError as e:\n    logger.error(f\"invalid tool name: {e}\")\n    raise","preventionTips":["Never embed source prefixes ('builtin:', 'mcp:') in the name — the add_* method sets the source","Sanitize names from external config: strip whitespace, replace '.'/'/' with '-'","Test any externally-sourced tool name against ^[a-zA-Z0-9_-]+$ before registration","Use source-qualified wildcards (add_mcp('*')) instead of encoding source in the name"],"tags":["validation","python","tools","regex"],"backgroundTag":"invalid-identifier-format","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}