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
- Perform all steps of an import plan (create, finalize, cancel) from the same renderer window that created it.
- Recreate the plan if the owning window was reloaded; old planIds become invalid.
- 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
- Create and consume import plans within a single renderer session.
- Discard planIds on window reload.
- Never pass planIds between windows or persist them.
- Track which window created each plan on the renderer side too.
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
- Extension folder import is available only from the…
- 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/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)