moeru-ai/airi · error · Error

Extension import plan is not available to this renderer.

Error message

Extension import plan is not available to this renderer.

What it means

This error is thrown when a renderer tries to operate on an extension import plan that it does not own. The main process keeps a planOwners map from planId to the creating renderer's webContents id; if the requesting sender id does not match the recorded owner, the plan is considered inaccessible and the request is rejected.

Solutions

  1. Perform all steps of an import plan (create, finalize, cancel) from the same renderer window that created it.
  2. Recreate the plan if the owning window was reloaded; old planIds become invalid.
  3. Do not share planIds across windows or persist them beyond the import session.

Example fix

// before
// window B tries to use a plan created by window A
await window.ipc.invoke('extension:finalize-import', plan.planId)

// after
// window B creates its own plan
const plan = await window.ipc.invoke('extension:prepare-import')
await window.ipc.invoke('extension:finalize-import', plan.planId)
Defensive patterns

Strategy: try-catch

Validate before calling

const owner = planOwners.get(planId)
if (owner !== currentSenderId) throw new Error('plan does not belong to this renderer')

Type guard

function ownsPlan(planOwners: Map<string, number>, planId: string, senderId: number): boolean {
  return planOwners.get(planId) === senderId
}

Try / catch

try {
  await window.ipc.invoke('extension:finalize-import', planId)
} catch (err) {
  if (String((err as Error).message).includes('not available to this renderer')) {
    // recreate the plan in this window before continuing
  }
}

Prevention

When it happens

Trigger: Calling an import-plan IPC handler (e.g. finalize/cancel) with a planId whose recorded owner id differs from event.sender.id, or with a planId that was never registered for this sender.

Common situations: Forwarding a planId from one window to another; reusing a planId after the owning window reloaded (new sender id); guessing or persisting a stale planId across sessions.

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


AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17). Data as JSON: /api/errors/d4a36867b7daf986. Report an issue: GitHub.

Appendix: source

Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/index.ts:54

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) {
      continue
    }
    hostService.cancelDirectoryImport(planId)
    planOwners.delete(planId)
  }
}

function trackDirectoryImportOwner(

View on GitHub (pinned to 438a067dde)