{"record":{"id":"913406329ba9c400","repo":"tinyhumansai/openhuman","slug":"socket-not-connected-no-client-id-for-event-rout","errorCode":null,"errorMessage":"Socket not connected — no client ID for event routing","messagePattern":"Socket not connected — no client ID for event routing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/chatService.ts","lineNumber":1268,"sourceCode":"  queueMode?: QueueMode | null;\n}\n\n/**\n * Send a chat message via core RPC.\n *\n * The Rust core spawns the agent loop asynchronously and streams events\n * (tool_call, tool_result, chat_done, chat_error) back over the socket\n * connection using the `client_id` (socket ID) for routing.\n *\n * Returns the turn's `request_id` (from the RPC ack) when the core provides\n * one — used by `parallel` sends to register the forked turn's stream lane.\n * `undefined` if the ack carried no id.\n */\nexport async function chatSend(params: ChatSendParams): Promise<string | undefined> {\n  const socket = socketService.getSocket();\n  const clientId = socket?.id;\n  if (!clientId) {\n    throw new Error('Socket not connected — no client ID for event routing');\n  }\n\n  const result = await callCoreRpc({\n    method: 'openhuman.channel_web_chat',\n    params: {\n      client_id: clientId,\n      thread_id: params.threadId,\n      message: params.message,\n      model_override: params.model ?? undefined,\n      profile_id: params.profileId ?? undefined,\n      locale: params.locale ?? undefined,\n      speak_reply: params.speakReply ?? undefined,\n      source: params.source ?? undefined,\n      session_id: params.sessionId ?? undefined,\n      queue_mode: params.queueMode ?? undefined,\n    },\n  });\n","sourceCodeStart":1250,"sourceCodeEnd":1286,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/chatService.ts#L1250-L1286","documentation":"Thrown by chatSend() before sending: the chat turn is dispatched via 'openhuman.channel_web_chat' and the core streams the turn's events (tool_call, chat_done, chat_error) back over the socket keyed by client_id = socket.id. If socketService.getSocket() returns no socket or the socket has no id, the turn would be launched blind with no event lane, so the client refuses to send.","triggerScenarios":"User hits send while the socket is disconnected: core was restarted, network dropped, auth expired and the socket closed, or the app is in a state where the socket service never connected. Also fires in tests that call chatSend without mocking a connected socket.","commonSituations":"Socket reconnect lag after core restart (user types faster than reconnect); laptop sleep/resume; backend/auth expiry closing the socket; UI send button not gated on connectivity state.","solutions":["Gate the send action on socket connectivity (the Redux socket slice / socketService state) and disable/queue the composer while disconnected","Trigger or await socket reconnect before retrying the send — the message text should be preserved in the composer","If it persists, restart the core and let the app reconnect (Settings -> Restart Core drives the full reconnect path)","In tests, mock socketService.getSocket to return { id: 'test-socket' }"],"exampleFix":"// before\nconst requestId = await chatSend({ threadId, message });\n\n// after\nconst socket = socketService.getSocket();\nif (!socket?.id) {\n  setPendingMessage(message); // queue until reconnect\n  await socketService.reconnect();\n}\nconst requestId = await chatSend({ threadId, message });","handlingStrategy":"validation","validationCode":"const socket = socketService.getSocket();\nif (!socket?.id) {\n  setQueuedMessage(params); // preserve the draft\n  await ensureSocketConnected(); // reconnect before dispatching the turn\n}\nawait chatSend(params);","typeGuard":"function hasSocketId(s: unknown): s is { id: string } {\n  return !!s && typeof s === 'object' && typeof (s as { id?: unknown }).id === 'string' && (s as { id: string }).id.length > 0;\n}","tryCatchPattern":"try {\n  return await chatSend(params);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('no client ID')) {\n    await socketService.reconnect();\n    return chatSend(params); // message preserved by the caller\n  }\n  throw e;\n}","preventionTips":["Disable or queue the composer while the socket is disconnected (subscribe to the socket slice)","Reconnect proactively after core restarts and resume/sleep events","Never fire event-routed RPCs without a live socket id — by design they would stream nowhere"],"tags":["socket","realtime","chat","connectivity"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}