{"record":{"id":"9531608399a3d464","repo":"github/copilot-sdk","slug":"invalid-s-tool-name-q-tool-names-must-match","errorCode":null,"errorMessage":"invalid %s tool name %q: tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard %q","messagePattern":"invalid (.+?) tool name %q: tool names must match /\\^\\[a-zA-Z0-9_-\\]\\+\\$/ or be the wildcard %q","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/toolset.go","lineNumber":95,"sourceCode":"\treturn s\n}\n\n// ToSlice returns a defensive copy of the accumulated filter strings.\nfunc (s *ToolSet) ToSlice() []string {\n\tout := make([]string, len(s.items))\n\tcopy(out, s.items)\n\treturn out\n}\n\nfunc validateToolName(kind, name string) {\n\tif name == \"\" {\n\t\tpanic(fmt.Sprintf(\"invalid %s tool name: must not be empty\", kind))\n\t}\n\tif name == \"*\" {\n\t\treturn\n\t}\n\tif !toolNameRegex.MatchString(name) {\n\t\tpanic(fmt.Sprintf(\n\t\t\t\"invalid %s tool name %q: tool names must match /^[a-zA-Z0-9_-]+$/ or be the wildcard %q\",\n\t\t\tkind, name, \"*\"))\n\t}\n}\n\n// BuiltInToolsIsolated lists built-in tools that operate only within the\n// bounds of a single session — no host filesystem access outside the session,\n// no cross-session state, no host environment access, no network. Safe to\n// enable in [ModeEmpty] scenarios (e.g. multi-tenant servers) without leaking\n// host capabilities.\n//\n// Contract: tools in this set MUST NOT be extended (even behind options or\n// args) to read or write state outside the session boundary. Adding\n// cross-session or host-state behavior to one of these tools is a breaking\n// change that requires removing it from this set.\nvar BuiltInToolsIsolated = []string{\n\t\"ask_user\",\n\t\"task_complete\",","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/go/toolset.go#L77-L113","documentation":"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.","triggerScenarios":"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.","commonSituations":"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 \").","solutions":["Sanitize the name to the allowed charset (replace dots/colons/slashes with '-' or '_') before adding.","Strip provider prefixes/qualifiers so only the bare tool identifier is passed.","Trim whitespace and validate with a regex check in your own code before calling the Add method.","Use \"*\" if you actually intend to allow all tools."],"exampleFix":"// before\nts.AddMCP(\"mcp:filesystem.read\") // ':' invalid\n\n// after\nname := strings.NewReplacer(\":\", \"_\", \".\", \"_\").Replace(\"mcp:filesystem.read\")\nts.AddMCP(name) // \"mcp_filesystem_read\"","handlingStrategy":"validation","validationCode":"var toolNameRe = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)\nfunc addableToolName(n string) bool { return n == \"*\" || toolNameRe.MatchString(n) }","typeGuard":"func isValidToolName(n string) bool {\n    return n == \"*\" || regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString(n)\n}","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        if s, ok := r.(string); ok && strings.Contains(s, \"tool names must match\") {\n            log.Fatalf(\"invalid tool name: %s\", s)\n        }\n        panic(r)\n    }\n}()","preventionTips":["Run the same regex the library uses on names before calling Add*.","Strip provider prefixes and normalize separators (dots/colons to '_') when ingesting external tool listings.","Trim whitespace from names parsed from CSV/config inputs."],"tags":["go","panic","validation","naming","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-16T04:17:20.429Z"}