{"record":{"id":"e0b256595f2f3e5c","repo":"googleapis/mcp-toolbox","slug":"tool-type-q-already-registered-e0b256","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/dataplex/dataplexgeneratedatainsights/dataplexgeneratedatainsights.go","lineNumber":34,"sourceCode":"\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/goccy/go-yaml\"\n\t\"github.com/googleapis/mcp-toolbox/internal/sources\"\n\t\"github.com/googleapis/mcp-toolbox/internal/tools\"\n\t\"github.com/googleapis/mcp-toolbox/internal/tools/dataplex/dataplexcommon\"\n\t\"github.com/googleapis/mcp-toolbox/internal/util\"\n\t\"github.com/googleapis/mcp-toolbox/internal/util/parameters\"\n)\n\nconst resourceType string = \"dataplex-generate-data-insights\"\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\tProjectID() string\n\tGenerateDataInsights(ctx context.Context, location, resourcePath string, publish bool) (string, error)\n}\n\ntype Config struct {\n\ttools.ConfigBase `yaml:\",inline\"`","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/tools/dataplex/dataplexgeneratedatainsights/dataplexgeneratedatainsights.go#L16-L52","documentation":"This is a fail-fast panic raised during package initialization. Each tool package in the toolbox registers its config factory under a unique `resourceType` string (here \"dataplex-generate-data-insights\") in the global toolRegistry via tools.Register() (internal/tools/tools.go:43). Register() returns false when the type string is already present in the map, and the package's init() deliberately panics because a duplicate registration means two packages are competing for the same tool type key, which would silently break YAML config decoding.","triggerScenarios":"Importing (directly or transitively) two packages whose init() functions call tools.Register() with the same resourceType constant \"dataplex-generate-data-insights\" — typically after a copy-pasted tool package kept the old const, or a rename/re-creation of the tool package left both old and new packages imported by cmd/root.go or a prebuilt-config registration list.","commonSituations":"A developer copies an existing tool directory (e.g. another dataplex tool) to start a new tool and forgets to change the resourceType constant; a merge/rebase re-adds an import that was removed; a fork adds a tool with a colliding type string; blank-importing two tool registration files that both claim the type.","solutions":["Search the repo for \"dataplex-generate-data-insights\" (grep -rn 'dataplex-generate-data-insights') and find the two packages registering it; give the new tool a unique resourceType (e.g. kebab-case tool name per conventions).","Remove the stale/duplicate import or delete the obsolete tool package that no longer should be registered.","If the collision is with a forked/renamed package, ensure only one copy of the tool package exists on the module path and go.mod doesn't vendor both old and new versions.","Rebuild with `go build ./...` and run `go test ./internal/tools/...` to confirm the panic is gone."],"exampleFix":"// before (copied tool, duplicate type)\nconst resourceType string = \"dataplex-generate-data-insights\"\n\n// after (unique type for the new tool)\nconst resourceType string = \"dataplex-generate-data-insights-report\"","handlingStrategy":"validation","validationCode":"// Before building, assert the type string is unique across the repo:\n// grep -rn '\"dataplex-generate-data-insights\"' --include='*.go' . | grep 'resourceType\\|tools.Register'\n// Expect exactly ONE registration. In Go you can also guard a custom tool's 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 // or panic, matching project convention\n\t}\n}","typeGuard":"func isRegistered(resourceType string) bool {\n\t_, exists := registrySnapshot[resourceType] // expose registry contents in a test helper\n\treturn exists\n}","tryCatchPattern":"// Go panics in init() cannot be recovered within init itself; recover at the top of main if you must:\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 copying an existing tool package as a template, change resourceType (and the package name) as the very first edit.","Add a unit test that iterates all tool packages and asserts every resourceType is unique.","Name the constant after the kebab-case tool type per project convention so collisions are obvious in review.","Run `go build ./...` and `go vet` in CI before merging; the panic fires at init so any import-graph mistake surfaces immediately.","grep for the new type string before committing to confirm only one tools.Register call exists."],"tags":["go","init-panic","tool-registry","duplicate-registration"],"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"}