sequelize/sequelize · error · Error

Named listener ${listenerName} already exists for hook ${Str

Error message

Named listener ${listenerName} already exists for hook ${String(hookName)} on ${getName(this.#eventTarget)}.

What it means

Thrown by HookHandler.addListener when a listenerName is provided and another listener with the same name is already registered for that hook on the same target. Named listeners are deduplicated so re-registration (e.g. in hot-reload or repeated setup) is treated as a programmer error rather than silently stacking duplicates.

Source

Thrown at packages/core/src/hooks.ts:155

   *
   * Returns a function that can be called to deregister the listener.
   *
   * @param hookName
   * @param listener
   * @param listenerName
   */
  addListener<HookName extends keyof HookConfig>(
    hookName: HookName,
    listener: HookConfig[HookName],
    listenerName?: string,
  ): () => void {
    this.#assertValidHookName(hookName);

    if (listenerName) {
      const existingListener = this.#getNamedListener(hookName, listenerName);

      if (existingListener) {
        throw new Error(
          `Named listener ${listenerName} already exists for hook ${String(hookName)} on ${getName(this.#eventTarget)}.`,
        );
      }
    }

    this.#listeners.append(hookName, { callback: listener, listenerName });

    return () => {
      this.removeListener(hookName, listenerName || listener);
    };
  }

  addListeners(listeners: {
    [Key in keyof HookConfig]?: AllowArray<
      HookConfig[Key] | { name: string | symbol; callback: HookConfig[Key] }
    >;
  }) {
    for (const hookName of this.#validHookNames) {

View on GitHub (pinned to 7e1deec499)

Solutions

  1. Call removeListener(hook, 'myName') (or use the returned deregister function) before re-registering.
  2. Guard registration with a module-level flag so setup runs once.
  3. If duplicates are acceptable, omit listenerName and register anonymously.
  4. In tests, call removeAllListeners() in beforeEach.

Example fix

// before
model.addHook('beforeCreate', auditFn, 'audit');
// re-run on HMR
model.addHook('beforeCreate', auditFn, 'audit'); // throws

// after
const off = model.addHook('beforeCreate', auditFn, 'audit');
if (module.hot) module.hot.dispose(() => off());
Defensive patterns

Strategy: try-catch

Validate before calling

// Check before adding
if (handler.getListenerCount(hookName) > 0 && handler.hasNamed(hookName, 'audit')) { /* skip or remove first */ }

Try / catch

try { const off = model.addHook('beforeCreate', fn, 'audit'); } catch (e) { if (/already exists/.test(String(e))) { model.removeHook('beforeCreate', 'audit'); model.addHook('beforeCreate', fn, 'audit'); } else throw e; }

Prevention

When it happens

Trigger: Calling addListener(hook, fn, 'myName') twice without removing the first; a module's setup function running twice (HMR, repeated test setup); addListeners with a `{ name, callback }` object colliding with an earlier registration.

Common situations: Hot-module reload re-running model registration; test suites that re-import modules without clearing hooks; plugin loaded twice; refactoring that moved addListener into a frequently-called function.

Related errors


AI-assisted analysis of sequelize/sequelize@7e1deec499 (2026-08-03). Data as JSON: /data/errors/3a631d4e98553fd6.json. Report an issue: GitHub.