{"record":{"id":"3820b0ef6b5448a4","repo":"tinyhumansai/openhuman","slug":"mcp-health-operrorgeneric","errorCode":null,"errorMessage":"mcp.health.opErrorGeneric","messagePattern":"mcp\\.health\\.opErrorGeneric","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/src/components/channels/mcp/McpServersTab.tsx","lineNumber":448,"sourceCode":"    void fetchCatalog(\n      debouncedCatalogFilters.query,\n      debouncedCatalogFilters.transport,\n      catalogPage + 1,\n      true\n    );\n  };\n\n  // Bulk lifecycle actions for the health toolbar. One failure doesn't abort the\n  // batch (allSettled), and we always refresh status so the dots reflect reality\n  // — but if any call rejected we then throw so the toolbar can surface the\n  // failure (otherwise a partial/total failure would look like success).\n  const handleReconnectAll = useCallback(\n    async (serverIds: string[]) => {\n      log('reconnect all: %o', serverIds);\n      const results = await Promise.allSettled(serverIds.map(id => mcpClientsApi.connect(id)));\n      await fetchStatuses();\n      if (results.some(r => r.status === 'rejected')) {\n        throw new Error(t('mcp.health.opErrorGeneric'));\n      }\n    },\n    [fetchStatuses, t]\n  );\n\n  const handleDisconnectAll = useCallback(\n    async (serverIds: string[]) => {\n      log('disconnect all: %o', serverIds);\n      const results = await Promise.allSettled(serverIds.map(id => mcpClientsApi.disconnect(id)));\n      await fetchStatuses();\n      if (results.some(r => r.status === 'rejected')) {\n        throw new Error(t('mcp.health.opErrorGeneric'));\n      }\n    },\n    [fetchStatuses, t]\n  );\n\n  const selectedServer =","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/components/channels/mcp/McpServersTab.tsx#L430-L466","documentation":"Bulk reconnect: every selected id goes to mcpClientsApi.connect(id) through allSettled so one failure does not abort the batch; statuses are refreshed first so the health dots reflect reality, then if any promise rejected this generic error is thrown so the toolbar surfaces the partial/total failure — per-server detail lives in the refreshed statuses, not the exception.","triggerScenarios":"One or more connect(id) rejections inside a multi-select 'reconnect all' action: unreachable endpoint, expired auth, or an unknown server_id.","commonSituations":"Several installed servers where at least one has stale credentials or an offline endpoint.","solutions":["After the error, inspect the refreshed health dots / per-server status to identify which servers failed","Open the failing server's detail and reconnect it individually to see the real error","Fix each root cause (auth or endpoint) per server, then re-run the bulk action"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Optional pre-filter: skip servers that cannot reconnect (e.g. unauthorized ones)\nconst statuses = await mcpClientsApi.status();\nconst candidates = serverIds.filter(id => {\n  const s = statuses.find(x => x.server_id === id);\n  return s?.status !== 'unauthorized';\n});","typeGuard":"const hasRejected = (rs: PromiseSettledResult<unknown>[]): boolean =>\n  rs.some(r => r.status === 'rejected');","tryCatchPattern":"try {\n  await reconnectAll(ids);\n} catch (e) {\n  // partial failure by design: consult refreshed per-server statuses, not e\n  await fetchStatuses();\n  surfacePartialFailure();\n}","preventionTips":["Read this as 'at least one failed', never 'all failed' — statuses are refreshed before the throw","Reconnect failing servers individually to obtain the real per-server error","Fix auth/endpoint issues before re-running bulk actions"],"tags":["mcp","batch","reconnect","partial-failure"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}