{"record":{"id":"08a8deb179156473","repo":"googleapis/mcp-toolbox","slug":"tool-type-q-already-registered-08a8de","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/mysqlshowquerystats/mysqlshowquerystats.go","lineNumber":54,"sourceCode":"    count_star AS 'execution_count',\n    ROUND(sum_timer_wait / 1000000000, 2) AS 'total_latency_ms',\n    ROUND(avg_timer_wait / 1000000000, 2) AS 'average_latency_ms',\n    ROUND(max_timer_wait / 1000000000, 2) AS 'max_latency_ms',\n    sum_rows_sent AS 'total_rows_sent',\n    sum_rows_examined AS 'total_rows_examined',\n    sum_no_index_used AS 'full_table_scan_count',\n    sum_no_good_index_used AS 'inefficient_index_used_count',\n    last_seen AS 'last_executed'\nFROM performance_schema.events_statements_summary_by_digest\nWHERE schema_name NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')\nAND (schema_name = COALESCE(NULLIF(?, ''), NULLIF(DATABASE(), '')) OR COALESCE(NULLIF(?, ''), NULLIF(DATABASE(), '')) IS NULL)\nORDER BY sum_timer_wait 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\tPerformanceSchemaEnabled(context.Context) (bool, error)\n}\n","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/tools/mysql/mysqlshowquerystats/mysqlshowquerystats.go#L36-L72","documentation":"This is a fail-fast panic raised during package initialization. The mysqlshowquerystats package registers its factory under resourceType \"mysql-show-query-stats\" in the global toolRegistry via tools.Register() (internal/tools/tools.go:43). When Register() returns false — the map already contains that type key — the init() panics so that duplicate tool-type claims fail the build/startup immediately instead of nondeterministically overriding a factory.","triggerScenarios":"A second init() in the same binary calls tools.Register(\"mysql-show-query-stats\", ...): a copy of this package kept after a refactor, the same package linked under two import paths (e.g. via a replace/fork), or a hand-written tool reusing this type string in its own registration.","commonSituations":"Template-copying another mysql performance_schema tool and leaving the const unchanged; merging branches that each import a variant of the tool; a monorepo tool override colliding with the stock toolbox tool.","solutions":["grep -rn 'mysql-show-query-stats' to identify both registering packages; assign the new tool a unique resourceType.","Delete or unimport the obsolete duplicate package (check cmd/root.go imports).","Deduplicate module copies (go mod tidy; remove duplicate replace directives).","Verify with `go build ./...` and `go test ./internal/tools/mysql/...`."],"exampleFix":"// before\nconst resourceType string = \"mysql-show-query-stats\" // in both original and copied package\n\n// after\nconst resourceType string = \"mysql-show-query-stats\" // original only; copied tool uses \"mysql-statement-digest-stats\"","handlingStrategy":"validation","validationCode":"// Verify uniqueness before building:\n// grep -rn '\"mysql-show-query-stats\"' --include='*.go' . | grep 'resourceType\\|tools.Register'\n// Exactly one match expected. Optional soft guard for 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:\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 starting from a copied tool file, make changing resourceType the first commit-level edit and verify with grep.","Add a CI test asserting all registered resourceType strings are unique.","Prefer creating a new package directory with its own name over editing copies in place.","Avoid multiple replace/vendor copies of the toolbox module in dependents.","Run go build ./... locally; the init panic fires immediately on import."],"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"}