tinyhumansai/openhuman · error

Socket not connected — no client ID for event routing

Error message

Socket not connected — no client ID for event routing

What it means

Thrown by chatSend() before any RPC is issued: socketService.getSocket() returned no socket or a socket without an `id`. The chat pipeline is socket-routed — the core spawns the agent loop and streams tool_call/tool_result/chat_done/chat_error events keyed by client_id (the socket session id). Without a connected socket the turn's events would be undeliverable, so the send is refused up front rather than starting an orphaned turn.

Solutions

  1. Wait for the socket to be connected before enabling send: gate the composer on the socket connection state from SocketProvider.
  2. Retry after reconnect: listen for the socket 'connect' event and re-attempt the send (message text is still in state).
  3. If it persists, check core socketio service health (core logs, /health) — the backend of the socket may be down, keeping the client disconnected.
  4. In tests, provide a mock socketService whose getSocket() returns {id: 'test'} before invoking chatSend.

Example fix

// before
const socket = socketService.getSocket();
const clientId = socket?.id;
if (!clientId) {
  throw new Error('Socket not connected — no client ID for event routing');
}

// after — await reconnection with a bounded wait instead of failing instantly
async function awaitClientId(timeoutMs = 5000): Promise<string> {
  const deadline = Date.now() + timeoutMs;
  for (;;) {
    const id = socketService.getSocket()?.id;
    if (id) return id;
    if (Date.now() > deadline) throw new Error('Socket not connected — no client ID for event routing');
    await new Promise(r => setTimeout(r, 100));
  }
}
Defensive patterns

Strategy: validation

Validate before calling

const clientId = socketService.getSocket()?.id;
if (!clientId) {
  // disable the composer / queue the message until socket 'connect' fires
  setSendingDisabled(true);
  socketService.once('connect', () => setSendingDisabled(false));
}

Type guard

function hasSocketClientId(socket: unknown): socket is { id: string } {
  return Boolean(socket) && typeof (socket as { id?: unknown } | null)?.id === 'string';
}

Try / catch

try {
  await chatSend(params);
} catch (e) {
  if (e instanceof Error && e.message === 'Socket not connected — no client ID for event routing') {
    await socketService.waitForConnect(); // resubscribe on 'connect'
    await chatSend(params); // retry once connected
  } else throw e;
}

Prevention

When it happens

Trigger: User submits a message before SocketProvider finishes connecting; socket dropped (core restarted, network blip, laptop sleep/resume) and reconnection hasn't completed; running outside the socket-enabled host; a race at app boot where the chat UI mounts before the socket handshake.

Common situations: Sending immediately after app open or after core restart; transient disconnects on flaky networks; tests that mount chat components without a SocketProvider mock; socket connect blocked in restrictive network environments.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/913406329ba9c400. Report an issue: GitHub.

Appendix: source

Thrown at app/src/services/chatService.ts:1268

  queueMode?: QueueMode | null;
}

/**
 * Send a chat message via core RPC.
 *
 * The Rust core spawns the agent loop asynchronously and streams events
 * (tool_call, tool_result, chat_done, chat_error) back over the socket
 * connection using the `client_id` (socket ID) for routing.
 *
 * Returns the turn's `request_id` (from the RPC ack) when the core provides
 * one — used by `parallel` sends to register the forked turn's stream lane.
 * `undefined` if the ack carried no id.
 */
export async function chatSend(params: ChatSendParams): Promise<string | undefined> {
  const socket = socketService.getSocket();
  const clientId = socket?.id;
  if (!clientId) {
    throw new Error('Socket not connected — no client ID for event routing');
  }

  const result = await callCoreRpc({
    method: 'openhuman.channel_web_chat',
    params: {
      client_id: clientId,
      thread_id: params.threadId,
      message: params.message,
      model_override: params.model ?? undefined,
      profile_id: params.profileId ?? undefined,
      locale: params.locale ?? undefined,
      speak_reply: params.speakReply ?? undefined,
      source: params.source ?? undefined,
      session_id: params.sessionId ?? undefined,
      queue_mode: params.queueMode ?? undefined,
    },
  });

View on GitHub (pinned to 7491200858)