moeru-ai/airi · error · Error
The Extension management window is no longer available.
Error message
The Extension management window is no longer available.
What it means
Thrown during the directory-import flow when BrowserWindow.fromWebContents(event.sender) returns null, meaning the window that owned the sending webContents no longer exists. The flow needs the owner window for security-scoped bookmark tracking and ownership bookkeeping, so it aborts instead of continuing with a detached sender.
Solutions
- Keep the Extension management window open until the folder-import request completes.
- Guard the renderer side: disable the import action on window close and check window liveness before invoking.
- Handle the rejection in the renderer gracefully (it cannot recover once the window is gone).
Example fix
// before
const plan = await window.ipc.invoke('extension:select-folder') // invoked just before window.close()
window.close()
// after
const plan = await window.ipc.invoke('extension:select-folder')
if (plan) window.close() Defensive patterns
Strategy: try-catch
Validate before calling
if (ownerWindow?.isDestroyed()) throw new Error('window gone; abort import before invoking') Type guard
function hasAliveWindow(win: Electron.BrowserWindow | null): win is Electron.BrowserWindow {
return win !== null && !win.isDestroyed()
} Try / catch
try {
const plan = await window.ipc.invoke('extension:select-folder')
} catch (err) {
if (String((err as Error).message).includes('no longer available')) {
// window closed mid-request; surface a neutral message to the user
}
} Prevention
- Prevent window close while a folder-picker request is in flight.
- Check win.isDestroyed() before dependent operations.
- Avoid invoking import handlers from webviews or detached frames.
- Handle in-flight promises on window 'close' events.
When it happens
Trigger: The user closes the Extension management window between starting the directory-picker IPC call and the point where the main process resolves the owning BrowserWindow, or the sender is a frameless/webview context with no associated BrowserWindow.
Common situations: User closes the settings/extension window while a native folder dialog operation is in flight; window destroyed mid-request during app shutdown; sender webview detached from its host window.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Computer use has stopped.
- Electron IPC is not available in this renderer context
- Electron ipcRenderer is not available. Pass it explicitly…
- Extension folder import is available only from the…
- Extension import plan is not available to this renderer.
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/8ec6367ab2d70481.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/index.ts:128
const invokePluginProtocolListProviders = defineInvoke(context, pluginProtocolListProviders)
// A plan remains owned by the renderer that displayed its review. Reopening
// Settings creates a new renderer, which must start a new import review.
const directoryImportPlanOwners = new Map<string, number>()
const trackedDirectoryImportOwnerIds = new Set<number>()
defineInvokeHandler(context, electronPluginList, async () => {
return await hostService.list()
})
defineInvokeHandler(context, electronPluginPrepareDirectoryImport, async (_, invokeOptions) => {
const event = requireExtensionManagementEvent(invokeOptions, options.getExtensionManagementWebContentsId)
const dialogOptions: OpenDialogOptions = {
properties: ['openDirectory'],
securityScopedBookmarks: true,
}
const ownerWindow = BrowserWindow.fromWebContents(event.sender)
if (!ownerWindow) {
throw new Error('The Extension management window is no longer available.')
}
trackDirectoryImportOwner(
hostService,
directoryImportPlanOwners,
trackedDirectoryImportOwnerIds,
ownerWindow,
event.sender.id,
)
const selection = await dialog.showOpenDialog(ownerWindow, dialogOptions)
const sourcePath = selection.filePaths[0]
if (selection.canceled || !sourcePath) {
return { status: 'cancelled' as const }
}
const plan = await hostService.prepareDirectoryImport(sourcePath, selection.bookmarks?.[0])
if (
ownerWindow.isDestroyed()
|| options.getExtensionManagementWebContentsId?.() !== event.sender.idView on GitHub (pinned to 438a067dde)