{"id":"3a631d4e98553fd6","repo":"sequelize/sequelize","slug":"named-listener-listenername-already-exists-for","errorCode":null,"errorMessage":"Named listener ${listenerName} already exists for hook ${String(hookName)} on ${getName(this.#eventTarget)}.","messagePattern":"Named listener (.+?) already exists for hook (.+?) on (.+?)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/hooks.ts","lineNumber":155,"sourceCode":"   *\n   * Returns a function that can be called to deregister the listener.\n   *\n   * @param hookName\n   * @param listener\n   * @param listenerName\n   */\n  addListener<HookName extends keyof HookConfig>(\n    hookName: HookName,\n    listener: HookConfig[HookName],\n    listenerName?: string,\n  ): () => void {\n    this.#assertValidHookName(hookName);\n\n    if (listenerName) {\n      const existingListener = this.#getNamedListener(hookName, listenerName);\n\n      if (existingListener) {\n        throw new Error(\n          `Named listener ${listenerName} already exists for hook ${String(hookName)} on ${getName(this.#eventTarget)}.`,\n        );\n      }\n    }\n\n    this.#listeners.append(hookName, { callback: listener, listenerName });\n\n    return () => {\n      this.removeListener(hookName, listenerName || listener);\n    };\n  }\n\n  addListeners(listeners: {\n    [Key in keyof HookConfig]?: AllowArray<\n      HookConfig[Key] | { name: string | symbol; callback: HookConfig[Key] }\n    >;\n  }) {\n    for (const hookName of this.#validHookNames) {","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/sequelize/sequelize/blob/7e1deec499d5afbb8d1877c2f4d545cead1214ec/packages/core/src/hooks.ts#L137-L173","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call removeListener(hook, 'myName') (or use the returned deregister function) before re-registering.","Guard registration with a module-level flag so setup runs once.","If duplicates are acceptable, omit listenerName and register anonymously.","In tests, call removeAllListeners() in beforeEach."],"exampleFix":"// before\nmodel.addHook('beforeCreate', auditFn, 'audit');\n// re-run on HMR\nmodel.addHook('beforeCreate', auditFn, 'audit'); // throws\n\n// after\nconst off = model.addHook('beforeCreate', auditFn, 'audit');\nif (module.hot) module.hot.dispose(() => off());","handlingStrategy":"try-catch","validationCode":"// Check before adding\nif (handler.getListenerCount(hookName) > 0 && handler.hasNamed(hookName, 'audit')) { /* skip or remove first */ }","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["Use the returned deregister function on HMR/test teardown","Guard registration with a module-level flag","Omit the name to allow anonymous duplicates","Call removeAllListeners() in test beforeEach"],"tags":["hooks","lifecycle","registration","sequelize","events"],"analyzedSha":"7e1deec499d5afbb8d1877c2f4d545cead1214ec","analyzedAt":"2026-08-03T18:58:44.549Z","schemaVersion":2}