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

  1. Strip source prefixes: pass 'bash' not 'builtin:bash' to add_builtin (the kind is chosen by the method called)
  2. Sanitize the name: replace illegal characters with '-' or '_' and strip whitespace/newlines
  3. Use add_mcp('*') wildcard syntax instead of embedding source qualification in the name
  4. 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

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


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)