moeru-ai/airi · error · Error
AIRI host websocket bridge is unavailable
Error message
AIRI host websocket bridge is unavailable
What it means
The low-level throw inside postBridgeMessage in stage-pocket's websocket-bridge module. postBridgeMessage is called for connect/send/close commands and throws when neither window.AiriHostBridge nor window.webkit.messageHandlers.airiHostBridge is present. This is the root cause that the probe-level errors [0]/[1] guard against; reaching it directly means a bridge command was issued without the native shell being available.
Source
Thrown at apps/stage-pocket/src/modules/websocket-bridge.ts:45
onNativeMessage?: (payload: string) => void
}
}
}
const connections = new Map<string, HostBridgeConnection>()
function postBridgeMessage(command: HostBridgeCommand) {
if (window.AiriHostBridge) {
window.AiriHostBridge.postMessage(JSON.stringify(command))
return
}
if (window.webkit?.messageHandlers?.airiHostBridge) {
window.webkit.messageHandlers.airiHostBridge.postMessage(JSON.stringify(command))
return
}
throw new Error('AIRI host websocket bridge is unavailable')
}
function dispatchNativeEvent(payload: string) {
const event = JSON.parse(payload) as HostBridgeEvent
const connection = connections.get(event.id)
if (!connection) {
return
}
connection.handleNativeEvent(event)
}
class HostBridgeConnection {
readonly id = crypto.randomUUID()
private opened = false
private settled = false
constructor(View on GitHub (pinned to 27111382b4)
Solutions
- Always obtain the connector through getHostWebSocketConnector and discard it if the native shell reloads.
- In tests, stub window.AiriHostBridge = { postMessage: () => {} } before exercising connection logic.
- Guard send/close paths so they no-op when the bridge has disappeared rather than throwing.
Example fix
// before
postBridgeMessage({ kind: 'send', id, data })
// after
if (!window.AiriHostBridge && !window.webkit?.messageHandlers?.airiHostBridge) {
return false
}
postBridgeMessage({ kind: 'send', id, data }) Defensive patterns
Strategy: validation
Validate before calling
function assertBridgeForCommand(): boolean {
return !!(window.AiriHostBridge || window.webkit?.messageHandlers?.airiHostBridge)
}
// guard send/close paths:
if (!assertBridgeForCommand()) return false Type guard
function isHostBridgeInjected(): boolean {
return !!(window.AiriHostBridge || window.webkit?.messageHandlers?.airiHostBridge)
} Prevention
- Always obtain connectors through getHostWebSocketConnector; never call postBridgeMessage directly.
- No-op send/close when the bridge disappears instead of throwing.
- Stub the bridge global in unit tests.
When it happens
Trigger: Any HostBridgeConnection operation (constructor connect, send, or close) invoked when the native bridge globals are absent. getHostWebSocketConnector already short-circuits to undefined in that case, so hitting this raw throw implies a caller bypassed that check or the bridge vanished after the connector was created.
Common situations: Connector obtained while the bridge existed, then a send/close issued after the webview was reloaded or the native shell detached the handler; unit tests that construct the connector without mocking the globals; a code path that calls postBridgeMessage directly.
Related errors
- AIRI host websocket bridge is unavailable
- No candidate server channel URL was reachable. ${errors.join
- Godot stage bridge is not connected.
- No WebSocket constructor is available. Pass `wsConstructor`
- WebSocket transport is not implemented for node runtime yet.
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/2b4b5654fe1d36cd.
Report an issue: GitHub.