github/copilot-sdk · error · ValueError
invalid tool name : tool names must match…
Error message
invalid {kind} tool name {name!r}: tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard '*' What it means
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.
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
Example fix
// before
toolset.add_builtin('builtin:bash') # colon not allowed
// after
toolset.add_builtin('bash') # or toolset.add_builtin('*') for all builtin tools Defensive patterns
Strategy: validation
Validate before calling
import re
TOOL_NAME_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
if not (name == "*" or TOOL_NAME_RE.match(name)):
raise ValueError(f"invalid tool name {name!r}") Type guard
def is_valid_tool_name(name) -> bool:
return isinstance(name, str) and (name == "*" or bool(re.match(r"^[a-zA-Z0-9_-]+$", name))) Try / catch
try:
toolset.add_mcp(server_name)
except ValueError as e:
logger.error(f"invalid tool name: {e}")
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- invalid tool name: must not be empty
- invalid entry '*': there is no bare wildcard. Use…
- Invalid entry '*': there is no bare wildcard. Use `new…
- CopilotClient is in Mode = CopilotClientMode.Empty but the…
- tool filter must be a ToolSet or list[str], not str. Pass a…
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/ae9b62f858cc605d.
Report an issue: GitHub.
Appendix: source
Thrown at python/copilot/_mode.py:30
import re
from collections.abc import Iterable
from typing import TYPE_CHECKING, Any, Literal
if TYPE_CHECKING:
from .session import MemoryConfiguration
CopilotClientMode = Literal["copilot-cli", "empty"]
_TOOL_NAME_REGEX = re.compile(r"^[a-zA-Z0-9_-]+$")
def _validate_tool_name(kind: str, name: str) -> None:
if not name:
raise ValueError(f"invalid {kind} tool name: must not be empty")
if name == "*":
return
if not _TOOL_NAME_REGEX.match(name):
raise ValueError(
f"invalid {kind} tool name {name!r}: tool names must match "
r"/^[a-zA-Z0-9_-]+$/ or be the wildcard '*'"
)
class ToolSet:
"""Builder for source-qualified tool filter patterns.
``ToolSet`` accumulates entries like ``builtin:bash``, ``mcp:*``, or
``custom:my_tool`` for use in
:class:`CopilotClient.create_session`'s ``available_tools`` /
``excluded_tools`` parameters.
Tool classification (``builtin``/``mcp``/``custom``) is determined by the
runtime at registration time — not by name parsing — so
``add_builtin("foo")`` only matches tools the runtime registered as
built-in.
"""View on GitHub (pinned to cd8cf15dc3)