github/copilot-sdk · error

invalid tool name : tool names must match…

Error message

invalid %s tool name %q: tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard %q

What it means

validateToolName enforces that tool names either match /^[a-zA-Z0-9_-]+$/ or are exactly the wildcard "*". Names with spaces, dots, slashes, or other characters cannot be resolved by the tool-filter machinery, so AddBuiltIn/AddCustom/AddMCP panic with the offending name quoted. The message explicitly documents the accepted format.

Solutions

  1. Sanitize the name to the allowed charset (replace dots/colons/slashes with '-' or '_') before adding.
  2. Strip provider prefixes/qualifiers so only the bare tool identifier is passed.
  3. Trim whitespace and validate with a regex check in your own code before calling the Add method.
  4. Use "*" if you actually intend to allow all tools.

Example fix

// before
ts.AddMCP("mcp:filesystem.read") // ':' invalid

// after
name := strings.NewReplacer(":", "_", ".", "_").Replace("mcp:filesystem.read")
ts.AddMCP(name) // "mcp_filesystem_read"
Defensive patterns

Strategy: validation

Validate before calling

var toolNameRe = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
func addableToolName(n string) bool { return n == "*" || toolNameRe.MatchString(n) }

Type guard

func isValidToolName(n string) bool {
    return n == "*" || regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString(n)
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        if s, ok := r.(string); ok && strings.Contains(s, "tool names must match") {
            log.Fatalf("invalid tool name: %s", s)
        }
        panic(r)
    }
}()

Prevention

When it happens

Trigger: Calling AddBuiltIn("my.tool"), AddCustom("read files"), AddMCP("tools/*"), or any name containing characters outside [a-zA-Z0-9_-]; also full provider-qualified names like "mcp:fs.read" that include a colon.

Common situations: Copying tool identifiers from server listings that use dotted/colon-qualified names; including a path or extension in the tool name; whitespace from CSV parsing (" read ").

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/9531608399a3d464. Report an issue: GitHub.

Appendix: source

Thrown at go/toolset.go:95

	return s
}

// ToSlice returns a defensive copy of the accumulated filter strings.
func (s *ToolSet) ToSlice() []string {
	out := make([]string, len(s.items))
	copy(out, s.items)
	return out
}

func validateToolName(kind, name string) {
	if name == "" {
		panic(fmt.Sprintf("invalid %s tool name: must not be empty", kind))
	}
	if name == "*" {
		return
	}
	if !toolNameRegex.MatchString(name) {
		panic(fmt.Sprintf(
			"invalid %s tool name %q: tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard %q",
			kind, name, "*"))
	}
}

// BuiltInToolsIsolated lists built-in tools that operate only within the
// bounds of a single session — no host filesystem access outside the session,
// no cross-session state, no host environment access, no network. Safe to
// enable in [ModeEmpty] scenarios (e.g. multi-tenant servers) without leaking
// host capabilities.
//
// Contract: tools in this set MUST NOT be extended (even behind options or
// args) to read or write state outside the session boundary. Adding
// cross-session or host-state behavior to one of these tools is a breaking
// change that requires removing it from this set.
var BuiltInToolsIsolated = []string{
	"ask_user",
	"task_complete",

View on GitHub (pinned to cd8cf15dc3)