jestjs/jest · error · ValidationError
Watch plugin configuration error
Error message
Watch plugin configuration error
What it means
Thrown as a ValidationError from checkForConflicts when registering watch plugins: either two plugins claim the same key, or a plugin tries to register a key reserved internally (forbiddenOverwriteMessage). Jest aborts watch-mode setup because ambiguous key bindings would make the interactive menu unusable.
Source
Thrown at packages/jest-core/src/watch.ts:514
Watch plugin ${chalk.bold.red(
getPluginIdentifier(plugin),
)} attempted to register key ${chalk.bold.red(`<${key}>`)},
that is reserved internally for ${chalk.bold.red(
conflictor.forbiddenOverwriteMessage,
)}.
Please change the configuration key for this plugin.`.trim();
} else {
const plugins = [conflictor.plugin, plugin]
.map(p => chalk.bold.red(getPluginIdentifier(p)))
.join(' and ');
error = `
Watch plugins ${plugins} both attempted to register key ${chalk.bold.red(
`<${key}>`,
)}.
Please change the key configuration for one of the conflicting plugins to avoid overlap.`.trim();
}
throw new ValidationError('Watch plugin configuration error', error);
};
const getPluginIdentifier = (plugin: WatchPlugin) =>
// This breaks as `displayName` is not defined as a static, but since
// WatchPlugin is an interface, and it is my understanding interface
// static fields are not definable anymore, no idea how to circumvent
// this :-(
// @ts-expect-error: leave `displayName` be.
plugin.constructor.displayName || plugin.constructor.name;
const getPluginKey = (
plugin: WatchPlugin,
globalConfig: Config.GlobalConfig,
) => {
if (typeof plugin.getUsageInfo === 'function') {
return (plugin.getUsageInfo(globalConfig) || {key: null}).key;
}
View on GitHub (pinned to f49721c78e)
Solutions
- Read the conflict text — it names both plugins and the conflicting `<key>`.
- Pass an explicit `key` in the watchPlugins config tuple for the offender, e.g. `['jest-watch-typeahead/filename', { key: 'f' }]`.
- Disable or remove one of the colliding plugins.
- Upgrade the plugin to a version that allows key overrides, and avoid reserved keys listed in Jest's usage menu.
Example fix
// before
watchPlugins: [
['jest-watch-typeahead/filename'],
['my-custom-plugin', { key: 'f' }], // both want 'f'
]
// after
watchPlugins: [
['jest-watch-typeahead/filename', { key: 'f' }],
['my-custom-plugin', { key: 'm' }],
] Defensive patterns
Strategy: validation
Validate before calling
// dedupe watch plugin keys in config before passing to Jest
const keys = watchPlugins.map(p => (p[1] && p[1].key) || require(p[0]).default.key);
if (new Set(keys).size !== keys.length) throw new Error('duplicate watch plugin keys'); Type guard
const hasUniqueKeys = (plugins: Array<[string, {key?: string}]>) => new Set(plugins.map(([, o]) => o?.key).filter(Boolean)).size === plugins.filter(([, o]) => o?.key).length; Prevention
- Always pass an explicit `key` for every watch plugin entry.
- Avoid single-letter keys already used by Jest's menu (a,p,f,t,o,q,enter,etc.).
- Upgrade watch plugins together so key conflicts surface.
When it happens
Trigger: Configuring `watchPlugins` with two entries whose `getUsageInfo().key` collide (e.g. both return `'t'`), or a third-party watch plugin that hard-codes a key Jest reserves (a/p/f/q/t/o/enter/etc.).
Common situations: Adding `jest-watch-typeahead` alongside another plugin that also binds the same letter; a plugin version change that introduced a default key clash; mixing multiple watch plugins without explicit key config.
Related errors
- Jest: Got error running ${moduleName} - ${modulePath}, reaso
- Shard ${globalConfig.shard.shardIndex}/${globalConfig.shard.
- The --config option requires a JSON string literal, or a fil
- Cannot merge config in form of callback
- Whoops! Two projects resolved to the same config path: ${Str
AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03).
Data as JSON: /data/errors/f1a3151a5c48e265.json.
Report an issue: GitHub.