{"record":{"id":"ef09c4dcef285013","repo":"can1357/oh-my-pi","slug":"server-name-was-disconnected-during-initial-c","errorCode":null,"errorMessage":"Server \"${name}\" was disconnected during initial connection","messagePattern":"Server \"(.+?)\" was disconnected during initial connection","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/coding-agent/src/mcp/manager.ts","lineNumber":576,"sourceCode":"\t\t\t\t\t\tthis.#handleServerNotification(name, method, params);\n\t\t\t\t\t},\n\t\t\t\t\tonRequest: (method, params) => {\n\t\t\t\t\t\treturn this.#handleServerRequest(method, params);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t})().then(\n\t\t\t\tasync connection => {\n\t\t\t\t\t// Store original config (without resolved tokens) to keep\n\t\t\t\t\t// cache keys stable and avoid leaking rotating credentials.\n\t\t\t\t\tconnection.config = config;\n\t\t\t\t\tif (sources[name]) {\n\t\t\t\t\t\tconnection._source = sources[name];\n\t\t\t\t\t}\n\n\t\t\t\t\tif (this.#epoch !== connectionEpoch || this.#pendingConnections.get(name) !== connectionPromise) {\n\t\t\t\t\t\tthis.#detachConnection(name, connection);\n\t\t\t\t\t\tvoid disconnectServer(connection).catch(() => {});\n\t\t\t\t\t\tthrow new Error(`Server \"${name}\" was disconnected during initial connection`);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.#pendingConnections.delete(name);\n\t\t\t\t\tthis.#connections.set(name, connection);\n\t\t\t\t\tthis.#serverConfigs.set(name, config);\n\n\t\t\t\t\t// Wire auth refresh for HTTP-like transports so 401s trigger token refresh.\n\t\t\t\t\t// Gate on a resolvable managed credential, not on the auth block:\n\t\t\t\t\t// definition-only configs (url-keyed fallback) get Bearer injection\n\t\t\t\t\t// too and need the same mid-session refresh hook.\n\t\t\t\t\tif (\n\t\t\t\t\t\tisAuthRefreshableMCPTransport(connection.transport) &&\n\t\t\t\t\t\tlookupMcpOAuthCredential(this.#authStorage, config)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconnection.transport.onAuthError = async () => {\n\t\t\t\t\t\t\tconst refreshed = await this.#resolveAuthConfig(config, { forceRefresh: true });\n\t\t\t\t\t\t\tif (refreshed.type === \"http\" || refreshed.type === \"sse\") {\n\t\t\t\t\t\t\t\treturn refreshed.headers ?? null;","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/manager.ts#L558-L594","documentation":"During connectServers(), after a server's connection promise resolves, the manager checks whether the server was concurrently disconnected or the manager was reset (epoch changed) while connecting. If so, it detaches and disconnects the just-created connection and throws this error naming the server. This prevents registering connections for servers that were removed mid-connect (e.g. by a /mcp reload or disconnectAll).","triggerScenarios":"Calling connectServers() while, for the same server name: disconnectServer/disconnectAll runs, the manager epoch is bumped (reload), a duplicate connect for the same name supersedes the pending promise, or #detachConnection is invoked by another path during the handshake.","commonSituations":"User runs /mcp reload while servers are still connecting at startup; a slow-connecting server (slow stdio spawn or remote OAuth flow) finishes after the config was reloaded; two overlapping connectServers() calls racing for the same name; manager reset during shutdown while connections still initializing.","solutions":["Retry connectServers() after the concurrent operation (reload/disconnect) completes.","Avoid triggering /mcp reload or disconnectAll while initial connections are in flight; wait for connection to settle first.","Don't issue overlapping connectServers() calls for the same server; await the first call.","Treat the error as a signal the server was removed from config — verify it's still in the current config before reconnecting.","Check logs for which concurrent operation raced the connect."],"exampleFix":"// before\nvoid manager.connectServers(); // fire-and-forget; user reload races it\n// after\nawait manager.connectServers(); // await before allowing reload UI actions\nenableReloadButton();","handlingStrategy":"retry","validationCode":"// Only connect when no conflicting lifecycle operation is in flight\nif (manager.isReloading() || manager.isDisconnecting(name)) {\n  await manager.waitForSettled(); // or defer until reload completes\n}\nawait manager.connectServers();","typeGuard":"null","tryCatchPattern":"try {\n  await manager.connectServers();\n} catch (e) {\n  if (e instanceof Error && /was disconnected during initial connection/.test(e.message)) {\n    // server removed/reloaded mid-connect; retry once after settling\n    await Bun.sleep(250);\n    return manager.connectServers();\n  }\n  throw e;\n}","preventionTips":["Await connectServers() before enabling reload/disconnect UI actions.","Never fire overlapping connectServers() calls for the same server.","Treat this as a benign race in reload flows — retry after the operation settles.","Keep initial-connect timeouts reasonable so connections don't straddle user actions."],"tags":["mcp","race-condition","connection","lifecycle"],"backgroundTag":"connection-cancelled-during-setup","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}