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

  1. Read the conflict text — it names both plugins and the conflicting `<key>`.
  2. Pass an explicit `key` in the watchPlugins config tuple for the offender, e.g. `['jest-watch-typeahead/filename', { key: 'f' }]`.
  3. Disable or remove one of the colliding plugins.
  4. 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

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


AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03). Data as JSON: /data/errors/f1a3151a5c48e265.json. Report an issue: GitHub.