moeru-ai/airi · warning

Channel gateway stream error:

Error message

Channel gateway stream error:

What it means

The async pump that reads a channel's ReadableStream threw — the stream errored or reader.read() rejected — so event dispatch for that channel stops. The channel and its reader stay registered unless unregister() runs, and the pump exits after the warn. Events on that channel are silently not delivered until the stream is re-established.

Source

Thrown at packages/stage-ui/src/stores/mods/api/channel-gateway.ts:92

    channels.set(channel.name, channel)

    if (!channel.in)
      return

    const reader = channel.in.getReader()
    readers.set(channel.name, reader)

    const pump = async () => {
      try {
        while (true) {
          const result = await reader.read()
          if (result.done)
            break
          dispatch(result.value, { origin: channel.name })
        }
      }
      catch (error) {
        console.warn('Channel gateway stream error:', channel.name, error)
      }
    }

    void pump()
  }

  function unregister(name: string) {
    channels.delete(name)
    const reader = readers.get(name)
    if (reader) {
      reader.cancel().catch(() => undefined)
      readers.delete(name)
    }
  }

  function route(rule: GatewayRoute) {
    routes.push(rule)
  }

View on GitHub (pinned to 677329427f)

Solutions

  1. Re-register the channel after the transport reconnects so a fresh pump starts
  2. Treat AbortError from reader.cancel() as an expected exit, not a stream failure
  3. When pumps die across all channels, check the underlying WebSocket client status and reconnect logs
  4. Sequence register/unregister per channel (the readers map is the source of truth) to avoid double pumps
Defensive patterns

Strategy: try-catch

Validate before calling

if (readers.has(channel.name)) return // pump already running for this channel

Try / catch

const pump = async () => {
  try {
    while (true) {
      const result = await reader.read()
      if (result.done) break
      dispatch(result.value, { origin: channel.name })
    }
  }
  catch (error) {
    // reader.cancel() from unregister() races read(); AbortError here is an expected exit
    console.warn('Channel gateway stream error:', channel.name, error)
  }
}

Prevention

When it happens

Trigger: Underlying WebSocket closing with an error while the pump awaits reader.read(); the provider stream erroring (serialization or protocol mismatch); reader.cancel() from unregister() racing an in-flight read.

Common situations: Network blip closing the shared WebSocket; dev-mode HMR tearing down transports; concurrent register/unregister of the same channel.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/f8af138463c81749. Report an issue: GitHub.