{"record":{"id":"9e35bfbdb907bc29","repo":"googleapis/mcp-toolbox","slug":"tool-type-q-already-registered-9e35bf","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/mysqllisttablestats/mysqllisttablestats.go","lineNumber":72,"sourceCode":"WHERE\n  t.table_schema NOT IN ('sys', 'information_schema', 'mysql', 'performance_schema')\n  AND (t.table_schema = COALESCE(NULLIF(?, ''), NULLIF(DATABASE(), '')) OR COALESCE(NULLIF(?, ''), NULLIF(DATABASE(), '')) IS NULL)\n  AND (COALESCE(?, '') = '' OR t.table_name = ?)\nORDER BY\n  CASE ?\n    WHEN 'row_count' THEN row_count\n    WHEN 'rows_fetched' THEN rows_fetched\n    WHEN 'rows_inserted' THEN rows_inserted\n    WHEN 'rows_updated' THEN rows_updated\n    WHEN 'rows_deleted' THEN rows_deleted\n    ELSE ts.total_latency\n    END DESC\nLIMIT ?;\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\tMySQLDatabase() string\n}\n\ntype Config struct {","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/tools/mysql/mysqllisttablestats/mysqllisttablestats.go#L54-L90","documentation":"This is a fail-fast panic raised during package initialization. The mysqllisttablestats package registers its factory under resourceType \"mysql-list-table-stats\" in the global toolRegistry via tools.Register() (internal/tools/tools.go:43). A false return means the type string was already claimed by another init(), so the package panics rather than allowing a silent overwrite that would corrupt config decoding.","triggerScenarios":"Another package's init() also calls tools.Register(\"mysql-list-table-stats\", ...) — usually a duplicated/forked copy of this tool package, or this package imported twice under different module paths — causing the registry map lookup at internal/tools/tools.go:44 to find an existing entry.","commonSituations":"Cloning this file to create a related stats tool without changing the const on line 30; a git merge reintroducing an import; two replace directives pulling old and new versions of the module; a fork registering an identically named tool.","solutions":["grep -rn 'mysql-list-table-stats' to find both init() registrations; update the duplicate package's resourceType to a unique kebab-case name.","Remove the unnecessary import of the duplicate package from cmd/root.go or wherever it is linked.","Check go.mod / vendor/ for two copies of the module being linked into the binary.","Rebuild and run `go test ./internal/tools/mysql/mysqllisttablestats/...`."],"exampleFix":"// before\nconst resourceType string = \"mysql-list-table-stats\" // duplicated in a copied package\n\n// after\nconst resourceType string = \"mysql-list-table-storage-stats\" // unique in the new package","handlingStrategy":"validation","validationCode":"// Verify uniqueness before building:\n// grep -rn '\"mysql-list-table-stats\"' --include='*.go' . | grep 'resourceType\\|tools.Register'\n// Exactly one match expected. Optional soft guard in custom tools:\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":"// Recover at the process boundary (init panics are unrecoverable in init):\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":["Rename resourceType, package, and directory together whenever you fork a tool package.","Add a registry-uniqueness unit test to internal/tools tests.","Review import lists in cmd/root.go after merges to remove resurrected duplicates.","Run go mod tidy to prevent two module copies from registering the same package.","grep the type string before committing registration changes."],"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"}