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

  1. Rename one of the conflicting commands so each Name() is unique
  2. Check cmd.Name() implementations for copy-paste leftovers
  3. If runtime registration is intended, guard with a lookup or provide an unregister/replace API before calling Register
  4. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/263bb61ab3890053. Report an issue: GitHub.