Tencent/WeKnora · error
im: duplicate command registration: %s
Error message
im: duplicate command registration: %s
What it means
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.
Source
Thrown at internal/im/command_registry.go:20
import "strings"
// CommandRegistry maps slash-command names to their handlers.
type CommandRegistry struct {
commands map[string]Command
}
// NewCommandRegistry returns an empty registry.
func NewCommandRegistry() *CommandRegistry {
return &CommandRegistry{commands: make(map[string]Command)}
}
// Register adds cmd to the registry under its Name(). Panics on duplicate names
// to surface misconfiguration at startup rather than silently ignoring it.
func (r *CommandRegistry) Register(cmd Command) {
key := strings.ToLower(cmd.Name())
if _, exists := r.commands[key]; exists {
panic("im: duplicate command registration: " + key)
}
r.commands[key] = cmd
}
// Parse checks whether content is a slash-command and, if so, returns the
// matching Command and the remaining tokens as args.
//
// It returns (nil, nil, false) when:
// - content does not start with "/"
// - the first token after "/" has no registered handler
//
// Note: unrecognised slash-words are deliberately NOT matched here so that
// the caller can decide whether to treat them as unknown commands (show help)
// or pass them through to the QA pipeline (e.g. "/api/v2/users" paths).
// Use LooksLikeCommand to distinguish the two cases.
func (r *CommandRegistry) Parse(content string) (Command, []string, bool) {
content = strings.TrimSpace(content)
if !strings.HasPrefix(content, "/") {View on GitHub (pinned to 988cbb0330)
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
Example fix
// before
func (c *deployCmd) Name() string { return "deploy" }
// after
func (c *deployCmd) Name() string { return "deploy-preview" } // unique among registered commands Defensive patterns
Strategy: validation
Validate before calling
key := strings.ToLower(cmd.Name())
if _, exists := registry.commands[key]; exists {
return fmt.Errorf("duplicate command: %s", key)
}
registry.commands[key] = cmd Type guard
func canRegister(r *CommandRegistry, cmd Command) bool {
_, exists := r.commands[strings.ToLower(cmd.Name())]
return !exists
} Try / catch
func safeRegister(r *CommandRegistry, cmd Command) (err error) {
defer func() { if rec := recover(); rec != nil { err = fmt.Errorf("register failed: %v", rec) } }()
r.Register(cmd)
return nil
} Prevention
- 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()
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- failed to generate JWT secret: %v
- api-key policy declared for non-existent route(s): %s
- types.TenantIDContextKey not set in context
- failed to generate schema: %v
- failed to marshal schema: %v
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/263bb61ab3890053.
Report an issue: GitHub.