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
- Wait for the socket to be connected before enabling send: gate the composer on the socket connection state from SocketProvider.
- Retry after reconnect: listen for the socket 'connect' event and re-attempt the send (message text is still in state).
- If it persists, check core socketio service health (core logs, /health) — the backend of the socket may be down, keeping the client disconnected.
- 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
- Bind the composer's disabled state to the socket connection state from SocketProvider.
- Preserve the drafted message so a reconnect-and-retry is one click.
- Mock getSocket() → {id:'test'} in unit tests that exercise chatSend.
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)