moeru-ai/airi · error · Error

Gamelet `${bindingId}` is not open.

Error message

Gamelet `${bindingId}` is not open.

What it means

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.

Source

Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/kits/gamelet/orchestration.ts:59

        await widgetsManager.pushWidget({
          id: bindingId,
          componentName: 'extension-ui',
          componentProps,
          size: 'l',
        })
      }

      await widgetsManager.openWindow({ id: bindingId })
    },
    async configure(bindingId, payload) {
      await widgetsManager.updateWidget({
        id: bindingId,
        componentProps: createComponentProps(bindingId, payload),
      })
    },
    async request<TResponse = HostDataRecord>(bindingId: string, payload: HostDataRecord, options?: { timeoutMs?: number }): Promise<TResponse> {
      if (!widgetsManager.getWidgetSnapshot(bindingId)) {
        throw new Error(`Gamelet \`${bindingId}\` is not open.`)
      }

      return await widgetsManager.requestWidgetIframe<TResponse & Record<string, unknown>>(
        bindingId,
        payload,
        {
          timeoutMs: options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
        },
      ) as TResponse
    },
    async close(bindingId) {
      await widgetsManager.removeWidget(bindingId)
    },
    async isOpen(bindingId) {
      return Boolean(widgetsManager.getWidgetSnapshot(bindingId))
    },
    dispose() {},
  }

View on GitHub (pinned to 27111382b4)

Solutions

  1. Call open(bindingId) (openWindow) and await its completion before issuing any request to that bindingId.
  2. Guard each request with a snapshot existence check and re-open the widget if it is missing.
  3. Verify the bindingId used in request exactly matches the id used in open.
  4. If the widget may have been closed, listen for the close/removal event and cancel or re-establish pending requests.

Example fix

// before
const result = await orchestration.request(bindingId, payload)

// after
if (!widgetsManager.getWidgetSnapshot(bindingId)) {
  await orchestration.open(bindingId)
}
const result = await orchestration.request(bindingId, payload)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the gamelet widget is open before requesting
if (!widgetsManager.getWidgetSnapshot(bindingId)) {
  await orchestration.open(bindingId)
}
const result = await orchestration.request(bindingId, payload)

Type guard

function isGameletOpen(bindingId: string, mgr: { getWidgetSnapshot: (id: string) => unknown }): boolean {
  return Boolean(mgr.getWidgetSnapshot(bindingId))
}

Try / catch

try {
  return await orchestration.request(bindingId, payload, options)
} catch (e) {
  if (/is not open/.test(errorMessageFrom(e) ?? '')) {
    await orchestration.open(bindingId)
    return await orchestration.request(bindingId, payload, options)
  }
  throw e
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/d4b95a9902fdf238. Report an issue: GitHub.