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
- Keep the Disposable returned by registerKeymap and dispose it before re-registering
- Register keymaps only once in extension activation (guard against re-activation)
- 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
- Always keep and dispose the Disposable returned by registerKeymap
- Register keymaps exactly once per activation
- Use unique, extension-prefixed keymap names
- Deactivate properly so re-activation does not duplicate registrations
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
- Invalid key ${name} of registerKeymap
- Extension ${id} not registered!
- Extension ${id} not registered
- Client got disposed and can't be restarted.
- Client is currently stopping. Can only restart a full stoppe
AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31).
Data as JSON: /api/errors/3128ceffbd872923.
Report an issue: GitHub.