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

  1. Keep the Extension management window open until the folder-import request completes.
  2. Guard the renderer side: disable the import action on window close and check window liveness before invoking.
  3. 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

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


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.id

View on GitHub (pinned to 438a067dde)