{"record":{"id":"263bb61ab3890053","repo":"Tencent/WeKnora","slug":"im-duplicate-command-registration-s","errorCode":null,"errorMessage":"im: duplicate command registration: %s","messagePattern":"im: duplicate command registration: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/command_registry.go","lineNumber":20,"sourceCode":"\nimport \"strings\"\n\n// CommandRegistry maps slash-command names to their handlers.\ntype CommandRegistry struct {\n\tcommands map[string]Command\n}\n\n// NewCommandRegistry returns an empty registry.\nfunc NewCommandRegistry() *CommandRegistry {\n\treturn &CommandRegistry{commands: make(map[string]Command)}\n}\n\n// Register adds cmd to the registry under its Name(). Panics on duplicate names\n// to surface misconfiguration at startup rather than silently ignoring it.\nfunc (r *CommandRegistry) Register(cmd Command) {\n\tkey := strings.ToLower(cmd.Name())\n\tif _, exists := r.commands[key]; exists {\n\t\tpanic(\"im: duplicate command registration: \" + key)\n\t}\n\tr.commands[key] = cmd\n}\n\n// Parse checks whether content is a slash-command and, if so, returns the\n// matching Command and the remaining tokens as args.\n//\n// It returns (nil, nil, false) when:\n//   - content does not start with \"/\"\n//   - the first token after \"/\" has no registered handler\n//\n// Note: unrecognised slash-words are deliberately NOT matched here so that\n// the caller can decide whether to treat them as unknown commands (show help)\n// or pass them through to the QA pipeline (e.g. \"/api/v2/users\" paths).\n// Use LooksLikeCommand to distinguish the two cases.\nfunc (r *CommandRegistry) Parse(content string) (Command, []string, bool) {\n\tcontent = strings.TrimSpace(content)\n\tif !strings.HasPrefix(content, \"/\") {","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/command_registry.go#L2-L38","documentation":"CommandRegistry.Register panics when two commands map to the same (lowercased) name. The registry deliberately treats duplicate registration as a programming/config error and fails fast at startup instead of silently overwriting a command.","triggerScenarios":"Calling Register(cmd) where cmd.Name() lowercases to a key already present in the registry — typically because NewService wires two Command implementations with identical Name() values (e.g. after adding a new command or a copy-pasted struct whose Name() was not changed).","commonSituations":"Copy-pasting a command struct and forgetting to update Name(); two plugins registering overlapping names; a rename refactor that collided with an existing command; tests constructing NewService twice against a shared registry.","solutions":["Rename one of the conflicting commands so each Name() is unique","Check cmd.Name() implementations for copy-paste leftovers","If runtime registration is intended, guard with a lookup or provide an unregister/replace API before calling Register","Log all registered names at startup to spot collisions quickly"],"exampleFix":"// before\nfunc (c *deployCmd) Name() string { return \"deploy\" }\n// after\nfunc (c *deployCmd) Name() string { return \"deploy-preview\" } // unique among registered commands","handlingStrategy":"validation","validationCode":"key := strings.ToLower(cmd.Name())\nif _, exists := registry.commands[key]; exists {\n    return fmt.Errorf(\"duplicate command: %s\", key)\n}\nregistry.commands[key] = cmd","typeGuard":"func canRegister(r *CommandRegistry, cmd Command) bool {\n    _, exists := r.commands[strings.ToLower(cmd.Name())]\n    return !exists\n}","tryCatchPattern":"func safeRegister(r *CommandRegistry, cmd Command) (err error) {\n    defer func() { if rec := recover(); rec != nil { err = fmt.Errorf(\"register failed: %v\", rec) } }()\n    r.Register(cmd)\n    return nil\n}","preventionTips":["Keep command names unique; grep Name() implementations before adding a command","Centralize the list of commands and assert uniqueness in a unit test","Avoid copy-pasting command structs without updating Name()"],"tags":["go","panic","startup","duplicate-registration","im"],"backgroundTag":"duplicate-registration","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}