{"record":{"id":"1b038874ec0149eb","repo":"moeru-ai/airi","slug":"extension-folder-import-is-available-only-from-the-extension","errorCode":null,"errorMessage":"Extension folder import is available only from the Extension management window.","messagePattern":"Extension folder import is available only from the Extension management window\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/airi/plugins/index.ts","lineNumber":47,"sourceCode":"  electronPluginListAgentTools,\n  electronPluginListXsaiTools,\n  electronPluginToolsChanged,\n} from '../../../../shared/eventa/plugin/tools'\nimport { onAppBeforeQuit } from '../../../libs/bootkit/lifecycle'\nimport { setupExtensionHostServiceInternal } from './host'\n\ninterface ElectronInvokeOptions {\n  raw?: { ipcMainEvent?: IpcMainEvent }\n}\n\nfunction requireExtensionManagementEvent(\n  invokeOptions: ElectronInvokeOptions | undefined,\n  getAuthorizedWebContentsId: SetupExtensionHostOptions['getExtensionManagementWebContentsId'],\n): IpcMainEvent {\n  const event = invokeOptions?.raw?.ipcMainEvent\n  const authorizedWebContentsId = getAuthorizedWebContentsId?.()\n  if (!event || authorizedWebContentsId === undefined || event.sender.id !== authorizedWebContentsId) {\n    throw new Error('Extension folder import is available only from the Extension management window.')\n  }\n  return event\n}\n\nfunction assertImportPlanOwner(planOwners: Map<string, number>, planId: string, senderId: number): void {\n  if (planOwners.get(planId) !== senderId) {\n    throw new Error('Extension import plan is not available to this renderer.')\n  }\n}\n\nfunction cancelDirectoryImportsForOwner(\n  hostService: Awaited<ReturnType<typeof setupExtensionHostServiceInternal>>,\n  planOwners: Map<string, number>,\n  ownerId: number,\n): void {\n  for (const [planId, planOwnerId] of planOwners) {\n    if (planOwnerId !== ownerId) {\n      continue","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/apps/stage-tamagotchi/src/main/services/airi/plugins/index.ts#L29-L65","documentation":"This error is thrown by the AIRI Electron main process when an IPC request to import an extension folder arrives from a renderer that is not the authorized Extension management window. The main process compares the sender's webContents id against the id registered as the extension-management window; any mismatch (or a missing event/authorization) is rejected to prevent arbitrary renderers from opening native directory pickers and importing extensions.","triggerScenarios":"An IPC event reaches requireExtensionManagementEvent when: the invoke options carry no raw ipcMainEvent, no extension-management webContents id has been registered, or event.sender.id differs from that registered id — i.e. any renderer other than the Extension management window invokes the extension folder-import handler.","commonSituations":"Calling the import API from a secondary window, a devtools page, or an embedded webview; triggering the import before the Extension management window registers its webContents id; stale window replaced after reload so the registered id no longer matches the live sender.","solutions":["Invoke the extension folder-import handler only from the Extension management window renderer.","Ensure the Extension management window registers its webContents id via getExtensionManagementWebContentsId before issuing import requests.","After a window reload, re-register the new webContents id with the main process.","Do not wrap or forward this IPC call from other windows or background pages."],"exampleFix":"// before\n// called from an arbitrary renderer window\nawait window.ipc.invoke('extension:import-folder', path)\n\n// after\n// only in the Extension management window renderer\nif (isExtensionManagementWindow) {\n  await window.ipc.invoke('extension:import-folder', path)\n}","handlingStrategy":"try-catch","validationCode":"const authorizedId = await window.ipc.invoke('extension:get-management-webcontents-id')\nif (window.__webContentsId !== authorizedId) throw new Error('not the extension management window')","typeGuard":"function isAuthorizedSender(event: Electron.IpcMainEvent, authorizedId: number | undefined): boolean {\n  return typeof authorizedId === 'number' && event.sender.id === authorizedId\n}","tryCatchPattern":"try {\n  await window.ipc.invoke('extension:import-folder')\n} catch (err) {\n  if (String((err as Error).message).includes('Extension folder import is available only')) {\n    // route the action to the extension management window instead\n  }\n}","preventionTips":["Only trigger extension imports from the Extension management window UI.","Register the management window's webContents id at window creation, before any import calls.","Re-register the id after every window reload.","Never proxy import IPC events from other windows or webviews."],"tags":["electron","ipc","security","extension-host"],"backgroundTag":"permission-denied","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-17T01:14:42.644Z","contentChangedAt":"2026-09-17T01:14:42.644Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}