can1357/oh-my-pi · error · ToolError

cmux socket closed

Error message

cmux socket closed

What it means

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.

Source

Thrown at packages/coding-agent/src/tools/browser/cmux/socket-client.ts:93

	#connectPromise: Promise<void> | null = null;
	#connected = false;
	#disposed = false;
	#buffer = "";
	readonly #lineWaiters: LineWaiter[] = [];
	readonly #queue: RequestJob[] = [];
	#activeJob: RequestJob | null = null;
	#pumping = false;

	constructor(opts: { socketPath: string; password?: string; relayId?: string; relayToken?: string }) {
		this.#socketPath = opts.socketPath;
		this.#password = opts.password;
		this.#relayId = opts.relayId ?? process.env.CMUX_RELAY_ID;
		this.#relayToken = opts.relayToken ?? process.env.CMUX_RELAY_TOKEN;
	}

	async connect(): Promise<void> {
		if (this.#disposed) {
			throw new ToolError("cmux socket closed");
		}
		if (this.#connected && this.#socket && !this.#socket.destroyed) {
			return;
		}
		if (this.#connectPromise) {
			return await this.#connectPromise;
		}

		this.#connectPromise = this.#openSocket();
		try {
			await this.#connectPromise;
		} finally {
			this.#connectPromise = null;
		}
	}

	async request(
		method: string,

View on GitHub (pinned to 9690622007)

Solutions

  1. Create a new CmuxSocketClient instance instead of reusing the disposed one
  2. Check whether the code path calling connect() runs after session teardown and gate it on session liveness
  3. If the close was unintentional, audit who calls close() (double-close or early cleanup) and fix the lifecycle

Example fix

// before
if (!this.client) this.client = new CmuxSocketClient({ socketPath });
await this.client.connect(); // throws if disposed earlier
// after
if (!this.client || this.isDisposed) {
  this.client = new CmuxSocketClient({ socketPath });
}
await this.client.connect();
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await client.connect();
} catch (err) {
  if (err instanceof ToolError && err.message === 'cmux socket closed') {
    client = new CmuxSocketClient({ socketPath });
    await client.connect();
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/0d91ad0d070ac629. Report an issue: GitHub.