{"record":{"id":"519132f24a5df7d0","repo":"googleapis/mcp-toolbox","slug":"tool-type-q-already-registered-519132","errorCode":null,"errorMessage":"tool type %q already registered","messagePattern":"tool type %q already registered","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/tools/mysql/mysqllisttablesmissinguniqueindexes/mysqllisttablesmissinguniqueindexes.go","lineNumber":57,"sourceCode":"        information_schema.table_constraints tco\n        ON\n            tab.table_schema = tco.table_schema\n            AND tab.table_name = tco.table_name\n            AND tco.constraint_type IN ('PRIMARY KEY', 'UNIQUE')\n    WHERE\n        tco.constraint_type IS NULL\n        AND tab.table_schema NOT IN('mysql', 'information_schema', 'performance_schema', 'sys')\n        AND tab.table_type = 'BASE TABLE'\n        AND (COALESCE(?, '') = '' OR tab.table_schema = ?)\n    ORDER BY\n        tab.table_schema,\n        tab.table_name\n    LIMIT ?;\n`\n\nfunc init() {\n\tif !tools.Register(resourceType, newConfig) {\n\t\tpanic(fmt.Sprintf(\"tool type %q already registered\", resourceType))\n\t}\n}\n\nfunc newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {\n\tactual := Config{ConfigBase: tools.ConfigBase{Name: name}}\n\tif err := decoder.DecodeContext(ctx, &actual); err != nil {\n\t\treturn nil, err\n\t}\n\treturn actual, nil\n}\n\ntype compatibleSource interface {\n\tMySQLPool() *sql.DB\n\tRunSQL(context.Context, string, []any) (any, error)\n}\n\ntype Config struct {\n\ttools.ConfigBase `yaml:\",inline\"`","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/tools/mysql/mysqllisttablesmissinguniqueindexes/mysqllisttablesmissinguniqueindexes.go#L39-L75","documentation":"This is a fail-fast panic raised during package initialization. The tool package registers its config factory under the unique `resourceType` string \"mysql-list-tables-missing-unique-indexes\" in the global toolRegistry via tools.Register() (internal/tools/tools.go:43). When Register() returns false because another init() already claimed that exact type string, this init() panics instead of silently overwriting, preventing ambiguous YAML `kind` resolution at startup.","triggerScenarios":"Two init() functions call tools.Register(\"mysql-list-tables-missing-unique-indexes\", ...) in the same binary — e.g. a copied/renamed MySQL tool package still using this constant, or the same package being linked twice under different import paths.","commonSituations":"Copy-pasting a nearby mysql tool directory (mysqllisttablestats, mysqllisttables, etc.) as a template and forgetting to update resourceType at line 30; a merge that re-imports an old package into cmd/root.go; vendoring two versions of the toolbox module so the package initializes twice.","solutions":["grep -rn 'mysql-list-tables-missing-unique-indexes' to locate both registrations; make the new/renamed tool use a distinct resourceType constant.","Remove the duplicate import (usually in cmd/root.go) or delete the obsolete tool package.","Check go.mod/replace directives for duplicate module copies that would double-register the package.","Run `go build ./...` and `go test ./internal/tools/mysql/...` to verify."],"exampleFix":"// before\ncopy of mysqllisttablesmissinguniqueindexes kept: const resourceType string = \"mysql-list-tables-missing-unique-indexes\"\n\n// after\nconst resourceType string = \"mysql-list-tables-missing-unique-indexes\" // only in the original package; new tool gets its own unique type","handlingStrategy":"validation","validationCode":"// Before building, assert uniqueness of the type string:\n// grep -rn '\"mysql-list-tables-missing-unique-indexes\"' --include='*.go' . | grep 'resourceType\\|tools.Register'\n// Expect exactly one registration. Guard custom tool init():\nfunc init() {\n\tif !tools.Register(resourceType, newConfig) {\n\t\tfmt.Printf(\"WARNING: %s already registered; skipping duplicate init\\n\", resourceType)\n\t\treturn\n\t}\n}","typeGuard":"func isRegistered(resourceType string) bool {\n\t_, exists := registrySnapshot[resourceType]\n\treturn exists\n}","tryCatchPattern":"// Panics in init() cannot be recovered in init; recover in main if needed:\nfunc main() {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tif s, ok := r.(string); ok && strings.Contains(s, \"already registered\") {\n\t\t\t\tlog.Fatalf(\"duplicate tool registration: %s\", s)\n\t\t\t}\n\t\t\tpanic(r)\n\t\t}\n\t}()\n\trootCmd.Execute()\n}","preventionTips":["When templating a new MySQL tool from an existing one, update resourceType first and run goimports/gofmt to keep names consistent.","Add a uniqueness test over the tool registry that fails CI on any duplicate type string.","Delete old tool package directories rather than leaving them unimported in the tree.","Keep go.mod free of duplicate replace directives that can link two copies of the same package.","grep the type string before every commit that touches tool registration."],"tags":["go","init-panic","tool-registry","duplicate-registration","mysql"],"backgroundTag":"duplicate-tool-type-registration","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}