{"record":{"id":"0d91ad0d070ac629","repo":"can1357/oh-my-pi","slug":"cmux-socket-closed","errorCode":null,"errorMessage":"cmux socket closed","messagePattern":"cmux socket closed","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/cmux/socket-client.ts","lineNumber":93,"sourceCode":"\t#connectPromise: Promise<void> | null = null;\n\t#connected = false;\n\t#disposed = false;\n\t#buffer = \"\";\n\treadonly #lineWaiters: LineWaiter[] = [];\n\treadonly #queue: RequestJob[] = [];\n\t#activeJob: RequestJob | null = null;\n\t#pumping = false;\n\n\tconstructor(opts: { socketPath: string; password?: string; relayId?: string; relayToken?: string }) {\n\t\tthis.#socketPath = opts.socketPath;\n\t\tthis.#password = opts.password;\n\t\tthis.#relayId = opts.relayId ?? process.env.CMUX_RELAY_ID;\n\t\tthis.#relayToken = opts.relayToken ?? process.env.CMUX_RELAY_TOKEN;\n\t}\n\n\tasync connect(): Promise<void> {\n\t\tif (this.#disposed) {\n\t\t\tthrow new ToolError(\"cmux socket closed\");\n\t\t}\n\t\tif (this.#connected && this.#socket && !this.#socket.destroyed) {\n\t\t\treturn;\n\t\t}\n\t\tif (this.#connectPromise) {\n\t\t\treturn await this.#connectPromise;\n\t\t}\n\n\t\tthis.#connectPromise = this.#openSocket();\n\t\ttry {\n\t\t\tawait this.#connectPromise;\n\t\t} finally {\n\t\t\tthis.#connectPromise = null;\n\t\t}\n\t}\n\n\tasync request(\n\t\tmethod: string,","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/cmux/socket-client.ts#L75-L111","documentation":"CmuxSocketClient.connect() refuses to establish a new connection once the client has been disposed via close(), which sets the internal #disposed flag. This error signals the client instance is permanently shut down and cannot be reused. It is thrown immediately before any socket work is attempted.","triggerScenarios":"Calling connect() (directly or indirectly through the request pump loop) after close() has been called on the same CmuxSocketClient instance. Also fires when openBrowserHandle reuses a stale, already-closed client.","commonSituations":"Holding a client across a browser-session teardown; a race where a background pump retries connect() after the user closed the session; caching a client in a module-level variable and reusing it after shutdown.","solutions":["Create a new CmuxSocketClient instance instead of reusing the disposed one","Check whether the code path calling connect() runs after session teardown and gate it on session liveness","If the close was unintentional, audit who calls close() (double-close or early cleanup) and fix the lifecycle"],"exampleFix":"// before\nif (!this.client) this.client = new CmuxSocketClient({ socketPath });\nawait this.client.connect(); // throws if disposed earlier\n// after\nif (!this.client || this.isDisposed) {\n  this.client = new CmuxSocketClient({ socketPath });\n}\nawait this.client.connect();","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await client.connect();\n} catch (err) {\n  if (err instanceof ToolError && err.message === 'cmux socket closed') {\n    client = new CmuxSocketClient({ socketPath });\n    await client.connect();\n  } else throw err;\n}","preventionTips":["Recreate the client instead of reusing one after close()","Guard background pumps against running post-shutdown"],"tags":["lifecycle","socket","disposed"],"backgroundTag":"client-already-closed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}