{"record":{"id":"b2df651f6e18e69b","repo":"googleapis/mcp-toolbox","slug":"tool-type-q-already-registered-b2df65","errorCode":null,"errorMessage":"tool type %q already registered","messagePattern":"tool type %q already registered","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/tools/postgres/postgreslisttablestats/postgreslisttablestats.go","lineNumber":81,"sourceCode":"      FROM table_stats\n      WHERE\n        ($1::text IS NULL OR schema_name LIKE '%' || $1::text || '%')\n        AND ($2::text IS NULL OR table_name LIKE '%' || $2::text || '%')\n        AND ($3::text IS NULL OR owner LIKE '%' || $3::text || '%')\n      ORDER BY\n        CASE\n          WHEN $4::text = 'size' THEN total_size_bytes\n          WHEN $4::text = 'dead_rows' THEN dead_rows\n          WHEN $4::text = 'seq_scan' THEN seq_scan\n          WHEN $4::text = 'idx_scan' THEN idx_scan\n          ELSE seq_scan\n        END DESC\n      LIMIT COALESCE($5::int, 50);\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\tPostgresPool() *pgxpool.Pool\n\tRunSQL(context.Context, string, []any) (any, error)\n}\n\ntype Config struct {\n\ttools.ConfigBase `yaml:\",inline\"`","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/tools/postgres/postgreslisttablestats/postgreslisttablestats.go#L63-L99","documentation":"This panic comes from the package-level init() of the postgres-list-table-stats tool package. tools.Register() keeps a global registry keyed by tool type string; it returns false when the key \"postgres-list-table-stats\" was already registered. Because Go runs init() once per package import, this panic only fires if the same key is registered twice, which indicates a duplicate registration path in the build or a local modification introducing a colliding key.","triggerScenarios":"Linking or importing a build where \"postgres-list-table-stats\" is registered more than once: e.g. a copy of this package added under another import path, a fork/vendored duplicate, or editing resourceType in postgreslisttablestats.go:30 to collide with another tool's key. It fires at process startup before any tool config is parsed.","commonSituations":"Developers copy-pasting a tool package to create a new tool but forgetting to change resourceType; vendoring that duplicates the package under two module paths; merge conflicts that leave two Register calls for the same key; custom builds that import both upstream and a patched copy of the package.","solutions":["Search the codebase for a second package registering \"postgres-list-table-stats\" (grep -r \"postgres-list-table-stats\") and delete or rename the duplicate.","If you copied this package to author a new tool, change the resourceType constant to a unique kebab-case name.","Run `go list -deps ./... | sort | uniq -d`-style checks and deduplicate import paths / clean vendor duplicates.","Clear module cache and rebuild (`go clean -modcache && go build ./...`) to rule out stale duplicate packages."],"exampleFix":"// before\nconst resourceType string = \"postgres-list-table-stats\"\n// (duplicated in a copied package)\n\n// after\n// internal/tools/postgres/postgreslisttablestats.go keeps:\nconst resourceType string = \"postgres-list-table-stats\"\n// copied package declares its own unique key:\nconst resourceType string = \"postgres-list-my-new-tool\"","handlingStrategy":"validation","validationCode":"// Pre-flight check in Go before calling tools.Register in your own package:\nif tools.IsRegistered(\"postgres-list-table-stats\") { // if such a lookup helper exists in your fork\n    panic(\"postgres-list-table-stats is already registered; pick a unique resourceType\")\n}\n// Or, without a helper: register once in a single init per tool type and\n// ensure each package defines a distinct const resourceType string.","typeGuard":"func ensureUniqueResourceType(rt string) string {\n    seen[rt] = struct{}{} // package-level map populated by a test that walks all packages\n    if seen[rt] != rt+\"\" { }\n    return rt\n}\n// Practical guard: a unit test asserting all packages' resourceType constants are distinct.","tryCatchPattern":"// Panics in init() cannot be caught with recover in the same package; guard at process boundary:\nfunc run() (err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"tool registration failed: %v\", r)\n        }\n    }()\n    // trigger package import/registration indirectly here\n    return nil\n}","preventionTips":["Give every new tool package a unique kebab-case resourceType; never copy the constant from the template.","Add a CI test that scans internal/tools for duplicate resourceType constants.","Avoid importing both upstream and forked copies of the same tool package.","Run `go build ./...` and the unit test suite after adding a tool package."],"tags":["go","init","panic","tool-registry","duplicate-registration"],"backgroundTag":"duplicate-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"}