{"record":{"id":"ef9df000f29d664b","repo":"googleapis/mcp-toolbox","slug":"tool-type-q-already-registered-ef9df0","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/oracle/oracleexecutesql/oracleexecutesql.go","lineNumber":22,"sourceCode":"\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net/http\"\n\n\tyaml \"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/util\"\n\t\"github.com/googleapis/mcp-toolbox/internal/util/parameters\"\n)\n\nconst resourceType string = \"oracle-execute-sql\"\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\tOracleDB() *sql.DB\n\tRunSQL(context.Context, string, []any, bool) (any, error)\n}\n\ntype Config struct {\n\ttools.ConfigBase `yaml:\",inline\"`","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/tools/oracle/oracleexecutesql/oracleexecutesql.go#L4-L40","documentation":"This is a startup panic thrown by the tool package's init() when tools.Register (internal/tools/tools.go:43) finds that the global toolRegistry map already contains an entry for the tool type string. Each tool package registers a factory under a unique `resourceType` constant at process init time; a collision means two registrations for the same `type` string, so the toolbox refuses to guess which factory wins and crashes immediately. Because it fires in init(), the program never serves any requests.","triggerScenarios":"Linking/importing the internal/tools/oracle/oracleexecutesql package (resourceType \"oracle-execute-sql\") together with another package whose init() calls tools.Register with the identical string — typically a copy-pasted copy of this file, a renamed package that kept the old constant, or two files in the same binary both declaring `const resourceType string = \"oracle-execute-sql\"`.","commonSituations":"A developer forked/copied oracleexecutesql.go into a new package to create a variant tool but only renamed the package/directory, not the `resourceType` constant, then imported both into cmd/ or a test binary; merge conflicts resolved by keeping two copies; or a refactoring PR duplicated a tool under a new directory without updating the registered type.","solutions":["Grep the binary's imports for a second registration of \"oracle-execute-sql\" (rg 'oracle-execute-sql' internal/ cmd/) and change one of the `resourceType` constants to a unique kebab-case string.","If the duplicate is an intentional variant tool, rename BOTH the `resourceType` constant and the package so the type string matches the new tool's kebab-case name (e.g. \"oracle-execute-sql-v2\").","If the duplicate file is leftover dead code, delete the copied package and remove its import.","Rebuild and run `go build ./...` plus unit tests to confirm only one init() registers the type."],"exampleFix":"// before (copied package oracleexecutesqlvariant.go)\nconst resourceType string = \"oracle-execute-sql\"\n// after\nconst resourceType string = \"oracle-execute-sql-variant\"","handlingStrategy":"validation","validationCode":"package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/googleapis/mcp-toolbox/internal/tools\"\n)\n\n// Run before adding imports/registrations (e.g. in a test or build check):\nfunc ensureTypeFree(resourceType string) error {\n\tif resourceType == \"\" {\n\t\treturn fmt.Errorf(\"resourceType must be non-empty\")\n\t}\n\t// Register in a scratch namespace is not exposed; instead verify uniqueness\n\t// across the source tree before building:\n\t//   rg -F '\"oracle-execute-sql\"' internal/ cmd/  -> expect exactly 1 hit\n\tif err := tools.Register(\"__probe__\"+resourceType, nil); !err {\n\t\treturn fmt.Errorf(\"type %q collision pattern detected\", resourceType)\n\t}\n\treturn nil\n}\n\nfunc main() {\n\tif err := ensureTypeFree(\"oracle-execute-sql\"); err != nil {\n\t\tos.Exit(1)\n\t}\n}","typeGuard":"func isRegistered(resourceType string) bool {\n\treturn !tools.Register(resourceType+\"__probe__\", nil) // false => key was already taken\n}","tryCatchPattern":null,"preventionTips":["When copying an existing tool file to create a new tool, always rename the `resourceType` constant in the same commit as the package rename.","Follow the repo convention that `resourceType` equals the tool's kebab-case name and matches the directory name.","Add a smoke test that imports all tool packages and asserts the binary initializes without panicking.","Before building a custom binary, run `rg -F '\"<your-type-string>\"' internal/ cmd/` and confirm exactly one registration site.","Never import both an upstream tool package and a local fork of it into the same binary."],"tags":["go","init-panic","duplicate-registration","tool-registry"],"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"}