golang/go · error

bad tool name

Error message

bad tool name: %q

What it means

Returned by base.ToolPath(toolName) when ValidToolName(toolName) is false. The go command builds the expected binary path as filepath.Join(build.ToolDir, toolName)+ToolExeSuffix, so a malformed tool name would produce a nonsense path; validation rejects it up front. An empty string or any character outside the accepted set trips the guard.

Solutions

  1. Validate with base.ValidToolName(name) before calling ToolPath.
  2. Use the known tool-name constants (e.g. "vet", "build") rather than constructing names from input.
  3. Trim and reject empty strings at the call site.
  4. If the name originates from argv/config, sanitize to alphanumeric+underscore before forwarding.

Example fix

// before
p, err := base.ToolPath(name) // panics-style error when name=""

// after
if !base.ValidToolName(name) {
    return fmt.Errorf("invalid tool name %q", name)
}
p, err := base.ToolPath(name)
Defensive patterns

Strategy: validation

Validate before calling

if !base.ValidToolName(name) {
    return fmt.Errorf("rejecting invalid tool name %q", name)
}
path, err := base.ToolPath(name)

Type guard

func isValidToolName(s string) bool {
    if s == "" { return false }
    for _, r := range s {
        if r == '/' || r == '\\' || r == ' ' || unicode.IsControl(r) {
            return false
        }
    }
    return true
}

Prevention

When it happens

Trigger: Calling base.ToolPath("") or with a name containing path separators, spaces, or characters not allowed by ValidToolName (which rejects empty and non-simple names).

Common situations: Code that builds tool names dynamically from user/file input; off-by-one slicing producing an empty string; passing a path instead of a bare tool name.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/5df4aeadfc74f2b9. Report an issue: GitHub.

Appendix: source

Thrown at src/cmd/go/internal/base/tool.go:34

// Tool returns the path to the named builtin tool (for example, "vet").
// If the tool cannot be found, Tool exits the process.
func Tool(toolName string) string {
	toolPath, err := ToolPath(toolName)
	if err != nil && len(cfg.BuildToolexec) == 0 {
		// Give a nice message if there is no tool with that name.
		fmt.Fprintf(os.Stderr, "go: no such tool %q\n", toolName)
		SetExitStatus(2)
		Exit()
	}
	return toolPath
}

// ToolPath returns the path at which we expect to find the named tool
// (for example, "vet"), and the error (if any) from statting that path.
func ToolPath(toolName string) (string, error) {
	if !ValidToolName(toolName) {
		return "", fmt.Errorf("bad tool name: %q", toolName)
	}
	toolPath := filepath.Join(build.ToolDir, toolName) + cfg.ToolExeSuffix()
	err := toolStatCache.Do(toolPath, func() error {
		_, err := os.Stat(toolPath)
		return err
	})
	return toolPath, err
}

func ValidToolName(toolName string) bool {
	if toolName == "" {
		return false
	}
	for _, c := range toolName {
		switch {
		case 'a' <= c && c <= 'z', '0' <= c && c <= '9', c == '_':
		default:
			return false

View on GitHub (pinned to b6b368adc5)