{"record":{"id":"ec371ab855d61c39","repo":"beekeeper-studio/beekeeper-studio","slug":"menu-item-menuitem-id-already-exists","errorCode":null,"errorMessage":"Menu item ${menuItem.id} already exists","messagePattern":"Menu item (.+?) already exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/studio/src/store/modules/MenuBarModule.ts","lineNumber":80,"sourceCode":"        }\n\n        (parent.submenu as Electron.MenuItemConstructorOptions[]).push({\n          id: externalItem.id,\n          label: externalItem.label,\n          click: () => {\n            actionHandler.handleAction(externalItem.action);\n          },\n          accelerator: externalItem.accelerator,\n        });\n      }\n\n      return menus;\n    },\n  },\n  mutations: {\n    add(state, menuItem: ExternalMenuItem) {\n      if (state.externalMenu[menuItem.id]) {\n        throw new Error(`Menu item ${menuItem.id} already exists`);\n      }\n      state.externalMenu = {\n        ...state.externalMenu,\n        [menuItem.id]: menuItem,\n      };\n    },\n    remove(state, id: string) {\n      if (!state.externalMenu[id]) {\n        throw new Error(`Menu item ${id} does not exist`);\n      }\n      state.externalMenu = _.omit(state.externalMenu, id);\n    },\n  },\n};\n","sourceCodeStart":62,"sourceCodeEnd":95,"githubUrl":"https://github.com/beekeeper-studio/beekeeper-studio/blob/4e3e03e3223b2b71a1af8926d455f533b80af837/apps/studio/src/store/modules/MenuBarModule.ts#L62-L95","documentation":"MenuBarModule's 'add' Vuex mutation registers an ExternalMenuItem in state.externalMenu keyed by id. Menu item ids must be unique, so registering an id that already exists throws `Menu item ${id} already exists` rather than silently overwriting the existing entry.","triggerScenarios":"Dispatching/committing the 'add' mutation twice with the same menuItem.id — e.g. a plugin re-registering its menu on hot-reload or reconnect without first removing the old entry.","commonSituations":"Plugin reload during development (webpack/vite HMR re-running registration code); a plugin enabling/disabling cycle that re-adds without cleanup; two plugins shipping the same id.","solutions":["Remove the existing menu item before re-adding (commit 'remove' for that id, then 'add').","Make registration idempotent: check state.externalMenu[id] first and skip or update instead of adding.","Use a unique id per registration instance (e.g. include a plugin instance/version suffix)."],"exampleFix":"// before\nstore.commit('menuBar/add', item) // throws on hot-reload\n// after\nif (!store.state.menuBar.externalMenu[item.id]) {\n  store.commit('menuBar/add', item)\n}","handlingStrategy":"validation","validationCode":"if (store.state.menuBar.externalMenu[item.id]) {\n  store.commit('menuBar/remove', item.id) // or skip registration\n}\nstore.commit('menuBar/add', item)","typeGuard":"const isNotRegistered = (menu: Record<string, ExternalMenuItem>, id: string) => !Object.prototype.hasOwnProperty.call(menu, id)","tryCatchPattern":"try {\n  store.commit('menuBar/add', item)\n} catch (e) {\n  if (e.message.includes('already exists')) {\n    store.commit('menuBar/remove', item.id)\n    store.commit('menuBar/add', item) // idempotent re-register on hot-reload\n  } else throw e\n}","preventionTips":["Make menu registration idempotent — check existing ids before adding.","Remove menu items on plugin teardown so reloads can re-add cleanly.","Namespace plugin menu ids (plugin-name:action) to avoid collisions.","During HMR, hook reload cleanup to unregister previously added items."],"tags":["vuex","menu","duplicate-key","plugins"],"backgroundTag":"duplicate-id-registration","analyzedSha":"4e3e03e3223b2b71a1af8926d455f533b80af837","analyzedAt":"2026-08-31T23:21:23.379Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}