{"record":{"id":"fb6fba88e3af9e54","repo":"tinyhumansai/openhuman","slug":"socket-not-connected","errorCode":null,"errorMessage":"Socket not connected","messagePattern":"Socket not connected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/lib/mcp/transport.ts","lineNumber":119,"sourceCode":"  }\n\n  off(event: string, handler: MCPEventHandler): void {\n    if (!this.socket) return;\n    const fullEvent = `${this.eventPrefix}${event}`;\n    const handlersForEvent = this.eventHandlers.get(fullEvent);\n    const wrappedHandler = handlersForEvent?.get(handler);\n\n    if (wrappedHandler) {\n      this.socket.off(fullEvent, wrappedHandler);\n      handlersForEvent?.delete(handler);\n    } else {\n      this.socket.off(fullEvent, handler);\n    }\n  }\n\n  async request(request: MCPRequest, timeoutMs = 30000): Promise<MCPResponse> {\n    if (!this.socket?.connected) {\n      throw new Error('Socket not connected');\n    }\n\n    mcpLog('Sending request', { id: request.id, method: request.method, timeoutMs });\n\n    return new Promise<MCPResponse>((resolve, reject) => {\n      const timeout = setTimeout(() => {\n        this.requestHandlers.delete(request.id);\n        mcpError('Request timeout', { id: request.id, method: request.method, timeoutMs });\n        reject(new Error(`MCP request timeout after ${timeoutMs}ms`));\n      }, timeoutMs);\n\n      this.requestHandlers.set(request.id, (response: MCPResponse) => {\n        clearTimeout(timeout);\n        if (response.error) {\n          mcpError('Request error', {\n            id: request.id,\n            method: request.method,\n            error: sanitizeError(response.error),","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/lib/mcp/transport.ts#L101-L137","documentation":"transport.ts implements an MCP client transport over the app's Socket.IO connection (socketService). request() first checks this.socket?.connected and throws 'Socket not connected' when there is no live socket — before constructing the request promise. The MCP server proxying therefore depends entirely on the Socket.IO transport being established.","triggerScenarios":"Issuing any MCP request (tools/list, tools/call, prompts/get) while socket is null (never connected), disconnected (server restart, network drop, sleep/resume), or in a reconnect backoff window. Also when the MCP UI mounts and fires requests before the socket provider finishes connecting.","commonSituations":"App resumed from sleep with the socket still reconnecting; core restarted (update applied) invalidating the socket; dev hot-reload resetting socket state; offline/captive-network environments; race at startup where MCP panels query immediately.","solutions":["Await socket connection (or subscribe to a connected event) before issuing MCP requests.","Retry with backoff on 'Socket not connected' — Socket.IO auto-reconnects, so a short retry loop usually resolves it.","Disable/grey out MCP-dependent UI while connectivity state is down, showing a reconnecting indicator.","If the core was updated/restarted, wait for the app's socketService reconnect handshake to complete before re-opening MCP sessions."],"exampleFix":"// before\nconst res = await transport.request(req);\n\n// after\nasync function requestWithRetry(req: MCPRequest, tries = 3) {\n  for (let i = 0; i < tries; i++) {\n    try { return await transport.request(req); }\n    catch (e) {\n      if (String(e.message).includes('Socket not connected') && i < tries - 1) {\n        await waitForSocketConnected(); continue;\n      }\n      throw e;\n    }\n  }\n}","handlingStrategy":"retry","validationCode":"if (!transport.socket?.connected) {\n  await waitForSocketConnected(5000); // app-specific helper\n}\nconst res = await transport.request(req);","typeGuard":"function isSocketDisconnectedError(e: unknown): boolean {\n  return e instanceof Error && e.message === 'Socket not connected';\n}","tryCatchPattern":"for (let i = 0; i < 3; i++) {\n  try {\n    return await transport.request(req);\n  } catch (e) {\n    if (!isSocketDisconnectedError(e) || i === 2) throw e;\n    await waitForSocketConnected(2000);\n  }\n}","preventionTips":["Gate MCP requests on the socket connectivity state; disable MCP UI while disconnected.","Expect transient disconnects after sleep/resume or core restarts and retry with backoff.","Do not fire MCP requests in component mount effects before the socket provider connects."],"tags":["mcp","socket-io","connection","transport","reconnect"],"backgroundTag":"socket-disconnected","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}