{"record":{"id":"d4b95a9902fdf238","repo":"moeru-ai/airi","slug":"gamelet-bindingid-is-not-open","errorCode":null,"errorMessage":"Gamelet `${bindingId}` is not open.","messagePattern":"Gamelet `(.+?)` is not open\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/airi/plugins/kits/gamelet/orchestration.ts","lineNumber":59,"sourceCode":"        await widgetsManager.pushWidget({\n          id: bindingId,\n          componentName: 'extension-ui',\n          componentProps,\n          size: 'l',\n        })\n      }\n\n      await widgetsManager.openWindow({ id: bindingId })\n    },\n    async configure(bindingId, payload) {\n      await widgetsManager.updateWidget({\n        id: bindingId,\n        componentProps: createComponentProps(bindingId, payload),\n      })\n    },\n    async request<TResponse = HostDataRecord>(bindingId: string, payload: HostDataRecord, options?: { timeoutMs?: number }): Promise<TResponse> {\n      if (!widgetsManager.getWidgetSnapshot(bindingId)) {\n        throw new Error(`Gamelet \\`${bindingId}\\` is not open.`)\n      }\n\n      return await widgetsManager.requestWidgetIframe<TResponse & Record<string, unknown>>(\n        bindingId,\n        payload,\n        {\n          timeoutMs: options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n        },\n      ) as TResponse\n    },\n    async close(bindingId) {\n      await widgetsManager.removeWidget(bindingId)\n    },\n    async isOpen(bindingId) {\n      return Boolean(widgetsManager.getWidgetSnapshot(bindingId))\n    },\n    dispose() {},\n  }","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/apps/stage-tamagotchi/src/main/services/airi/plugins/kits/gamelet/orchestration.ts#L41-L77","documentation":"Thrown by the gamelet orchestration request() handler when widgetsManager.getWidgetSnapshot(bindingId) returns a falsy value, meaning no widget window is currently open for that binding id. The request path relies on an open iframe to route messages to, so calling request before openWindow (or after close) is invalid.","triggerScenarios":"A gamelet host calls request(bindingId, payload) without first calling open(bindingId), or after the widget was closed/removed. Also happens if openWindow was called but the widget snapshot isn't ready yet (not yet registered in widgetsManager).","commonSituations":"Lifecycle ordering bug — request fired before the open promise resolved; the widget crashed and was auto-removed; the bindingId passed to request differs from the one passed to open; the widget was closed by user action while a queued request was in flight.","solutions":["Call open(bindingId) (openWindow) and await its completion before issuing any request to that bindingId.","Guard each request with a snapshot existence check and re-open the widget if it is missing.","Verify the bindingId used in request exactly matches the id used in open.","If the widget may have been closed, listen for the close/removal event and cancel or re-establish pending requests."],"exampleFix":"// before\nconst result = await orchestration.request(bindingId, payload)\n\n// after\nif (!widgetsManager.getWidgetSnapshot(bindingId)) {\n  await orchestration.open(bindingId)\n}\nconst result = await orchestration.request(bindingId, payload)","handlingStrategy":"validation","validationCode":"// Ensure the gamelet widget is open before requesting\nif (!widgetsManager.getWidgetSnapshot(bindingId)) {\n  await orchestration.open(bindingId)\n}\nconst result = await orchestration.request(bindingId, payload)","typeGuard":"function isGameletOpen(bindingId: string, mgr: { getWidgetSnapshot: (id: string) => unknown }): boolean {\n  return Boolean(mgr.getWidgetSnapshot(bindingId))\n}","tryCatchPattern":"try {\n  return await orchestration.request(bindingId, payload, options)\n} catch (e) {\n  if (/is not open/.test(errorMessageFrom(e) ?? '')) {\n    await orchestration.open(bindingId)\n    return await orchestration.request(bindingId, payload, options)\n  }\n  throw e\n}","preventionTips":["Await open(bindingId) before issuing requests to that binding.","Cancel pending requests when the widget close event fires.","Encapsulate open-then-request in a single helper so callers can't skip the open step."],"tags":["gamelet","widgets","lifecycle","ipc"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}