neoclide/coc.nvim · error

keymap: "${name}" already exists.

Error message

keymap: "${name}" already exists.

What it means

Each coc keymap name must be unique: registerKeymap stores handlers under 'coc-<name>' and throws if that key already exists, preventing silent override of an existing <Plug>(coc-<name>) mapping.

Source

Thrown at src/core/keymaps.ts:79

  }

  public async doInsertKeymap(key: string, ...args: any[]): Promise<InsertKeymapResult> {
    let fn = this.insertKeymaps.get(key)
    if (!fn) {
      logger.error(`insert keymap for ${key} not found`)
      return []
    }
    let res = await Promise.resolve(fn(...args))
    return Array.isArray(res) ? res : []
  }

  /**
   * Register global <Plug>(coc-${key}) key mapping.
   */
  public registerKeymap(modes: MapMode[], name: string, fn: KeymapCallback, opts: KeymapOption = {}): Disposable {
    if (!name) throw new Error(`Invalid key ${name} of registerKeymap`)
    let key = `coc-${name}`
    if (this.keymaps.has(key)) throw new Error(`keymap: "${name}" already exists.`)
    const lhs = `<Plug>(${key})`
    opts = Object.assign({ sync: true, cancel: true, silent: true, repeat: false }, opts)
    let { nvim } = this
    this.keymaps.set(key, [fn, !!opts.repeat])
    let method = opts.sync ? 'request' : 'notify'
    for (let mode of modes) {
      if (mode == 'i') {
        const cancel = opts.cancel ? 1 : 0
        nvim.setKeymap(mode, lhs, `coc#_insert_key('${method}', '${key}', ${cancel})`, {
          expr: true,
          noremap: true,
          silent: opts.silent
        })
      } else {
        nvim.setKeymap(mode, lhs, `:${getKeymapModifier(mode, opts.cmd)}call coc#rpc#${method}('doKeymap', ['${key}'])<cr>`, {
          noremap: true,
          silent: opts.silent
        })

View on GitHub (pinned to 50e974d969)

Solutions

  1. Keep the Disposable returned by registerKeymap and dispose it before re-registering
  2. Register keymaps only once in extension activation (guard against re-activation)
  3. Choose a unique name to avoid collision with other extensions

Example fix

// before
keymaps.registerKeymap(['n'], 'myaction', handler)
keymaps.registerKeymap(['n'], 'myaction', handler2) // throws
// after
disposables.push(keymaps.registerKeymap(['n'], 'myaction', handler))
// re-registration: dispose previous first
Defensive patterns

Strategy: try-catch

Validate before calling

// registry is internal, but you can guard your own registrations
if (registeredNames.has(name)) return // already registered this session

Try / catch

try {
  disposables.push(keymaps.registerKeymap(['n'], name, handler))
} catch (e) {
  if (String(e.message).includes('already exists')) logger.warn(`keymap ${name} already registered`)
  else throw e
}

Prevention

When it happens

Trigger: Calling registerKeymap twice with the same name, e.g. an extension activating twice without disposing the previous Disposable, or two extensions claiming the same keymap name.

Common situations: Double activation after extension reload without cleanup; re-running init code in tests; plugin conflicts where two extensions register the same coc-<name> keymap.

Related errors


AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31). Data as JSON: /api/errors/3128ceffbd872923. Report an issue: GitHub.