{"record":{"id":"d4a36867b7daf986","repo":"moeru-ai/airi","slug":"extension-import-plan-is-not-available-to-this-renderer","errorCode":null,"errorMessage":"Extension import plan is not available to this renderer.","messagePattern":"Extension import plan is not available to this renderer\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/airi/plugins/index.ts","lineNumber":54,"sourceCode":"interface ElectronInvokeOptions {\n  raw?: { ipcMainEvent?: IpcMainEvent }\n}\n\nfunction requireExtensionManagementEvent(\n  invokeOptions: ElectronInvokeOptions | undefined,\n  getAuthorizedWebContentsId: SetupExtensionHostOptions['getExtensionManagementWebContentsId'],\n): IpcMainEvent {\n  const event = invokeOptions?.raw?.ipcMainEvent\n  const authorizedWebContentsId = getAuthorizedWebContentsId?.()\n  if (!event || authorizedWebContentsId === undefined || event.sender.id !== authorizedWebContentsId) {\n    throw new Error('Extension folder import is available only from the Extension management window.')\n  }\n  return event\n}\n\nfunction assertImportPlanOwner(planOwners: Map<string, number>, planId: string, senderId: number): void {\n  if (planOwners.get(planId) !== senderId) {\n    throw new Error('Extension import plan is not available to this renderer.')\n  }\n}\n\nfunction cancelDirectoryImportsForOwner(\n  hostService: Awaited<ReturnType<typeof setupExtensionHostServiceInternal>>,\n  planOwners: Map<string, number>,\n  ownerId: number,\n): void {\n  for (const [planId, planOwnerId] of planOwners) {\n    if (planOwnerId !== ownerId) {\n      continue\n    }\n    hostService.cancelDirectoryImport(planId)\n    planOwners.delete(planId)\n  }\n}\n\nfunction trackDirectoryImportOwner(","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/apps/stage-tamagotchi/src/main/services/airi/plugins/index.ts#L36-L72","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n// window B tries to use a plan created by window A\nawait window.ipc.invoke('extension:finalize-import', plan.planId)\n\n// after\n// window B creates its own plan\nconst plan = await window.ipc.invoke('extension:prepare-import')\nawait window.ipc.invoke('extension:finalize-import', plan.planId)","handlingStrategy":"try-catch","validationCode":"const owner = planOwners.get(planId)\nif (owner !== currentSenderId) throw new Error('plan does not belong to this renderer')","typeGuard":"function ownsPlan(planOwners: Map<string, number>, planId: string, senderId: number): boolean {\n  return planOwners.get(planId) === senderId\n}","tryCatchPattern":"try {\n  await window.ipc.invoke('extension:finalize-import', planId)\n} catch (err) {\n  if (String((err as Error).message).includes('not available to this renderer')) {\n    // recreate the plan in this window before continuing\n  }\n}","preventionTips":["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."],"tags":["electron","ipc","security","ownership"],"backgroundTag":"permission-denied","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-17T01:14:42.644Z","contentChangedAt":"2026-09-17T01:14:42.644Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}