moeru-ai/airi · error · Error
Extension folder import is available only from the…
Error message
Extension folder import is available only from the Extension management window.
What it means
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.
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.
Example fix
// before
// called from an arbitrary renderer window
await window.ipc.invoke('extension:import-folder', path)
// after
// only in the Extension management window renderer
if (isExtensionManagementWindow) {
await window.ipc.invoke('extension:import-folder', path)
} Defensive patterns
Strategy: try-catch
Validate before calling
const authorizedId = await window.ipc.invoke('extension:get-management-webcontents-id')
if (window.__webContentsId !== authorizedId) throw new Error('not the extension management window') Type guard
function isAuthorizedSender(event: Electron.IpcMainEvent, authorizedId: number | undefined): boolean {
return typeof authorizedId === 'number' && event.sender.id === authorizedId
} Try / catch
try {
await window.ipc.invoke('extension:import-folder')
} catch (err) {
if (String((err as Error).message).includes('Extension folder import is available only')) {
// route the action to the extension management window instead
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Extension import plan is not available to this renderer.
- Electron IPC is not available in this renderer context
- Electron ipcRenderer is not available. Pass it explicitly…
- initScreenCaptureForMain must be called before calling…
- initScreenCaptureForWindow should only be called once per…
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/1b038874ec0149eb.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/index.ts:47
electronPluginListAgentTools,
electronPluginListXsaiTools,
electronPluginToolsChanged,
} from '../../../../shared/eventa/plugin/tools'
import { onAppBeforeQuit } from '../../../libs/bootkit/lifecycle'
import { setupExtensionHostServiceInternal } from './host'
interface ElectronInvokeOptions {
raw?: { ipcMainEvent?: IpcMainEvent }
}
function requireExtensionManagementEvent(
invokeOptions: ElectronInvokeOptions | undefined,
getAuthorizedWebContentsId: SetupExtensionHostOptions['getExtensionManagementWebContentsId'],
): IpcMainEvent {
const event = invokeOptions?.raw?.ipcMainEvent
const authorizedWebContentsId = getAuthorizedWebContentsId?.()
if (!event || authorizedWebContentsId === undefined || event.sender.id !== authorizedWebContentsId) {
throw new Error('Extension folder import is available only from the Extension management window.')
}
return event
}
function assertImportPlanOwner(planOwners: Map<string, number>, planId: string, senderId: number): void {
if (planOwners.get(planId) !== senderId) {
throw new Error('Extension import plan is not available to this renderer.')
}
}
function cancelDirectoryImportsForOwner(
hostService: Awaited<ReturnType<typeof setupExtensionHostServiceInternal>>,
planOwners: Map<string, number>,
ownerId: number,
): void {
for (const [planId, planOwnerId] of planOwners) {
if (planOwnerId !== ownerId) {
continueView on GitHub (pinned to 438a067dde)