can1357/oh-my-pi · error · ToolError

Cmux relay authentication failed for ${endpoint.host}:${endp

Error message

Cmux relay authentication failed for ${endpoint.host}:${endpoint.port}

What it means

After the client signs the challenge nonce with HMAC-SHA256(relayToken) and sends the auth line, the relay must answer with a JSON object { ok: true }. If the response line is not valid JSON, or parses but lacks ok:true, authentication is considered failed and this ToolError is thrown. Note the JSON.parse failure and the ok!==true failure produce the same message.

Source

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

		const message = `relay_id=${challenge.relay_id}\nnonce=${challenge.nonce}\nversion=${challenge.version}`;
		const key = await globalThis.crypto.subtle.importKey(
			"raw",
			credentials.relayToken,
			{ name: "HMAC", hash: "SHA-256" },
			false,
			["sign"],
		);
		const mac = await globalThis.crypto.subtle.sign("HMAC", key, UTF8.encode(message));
		const authLine = JSON.stringify({
			relay_id: credentials.relayId,
			mac: Buffer.from(mac).toString("hex"),
		});
		const responseLine = await this.#sendLine(authLine, DEFAULT_CONNECT_TIMEOUT_MS);
		let response: unknown;
		try {
			response = JSON.parse(responseLine);
		} catch {
			throw new ToolError(`Cmux relay authentication failed for ${endpoint.host}:${endpoint.port}`);
		}
		if (!response || typeof response !== "object" || !("ok" in response) || response.ok !== true) {
			throw new ToolError(`Cmux relay authentication failed for ${endpoint.host}:${endpoint.port}`);
		}
	}

	#waitForConnect(socket: net.Socket): Promise<void> {
		const { promise, resolve, reject } = Promise.withResolvers<void>();
		const timer = setTimeout(() => {
			socket.destroy();
			reject(new ToolError(`Failed to connect to cmux socket at ${this.#socketPath}: timed out`));
		}, DEFAULT_CONNECT_TIMEOUT_MS);
		const cleanup = (): void => {
			clearTimeout(timer);
			socket.off("connect", onConnect);
			socket.off("error", onError);
		};
		const onConnect = (): void => {

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-sync CMUX_RELAY_TOKEN with the token the relay expects (regenerate from ~/.cmux/relay/<port>.auth or the relay's registration output)
  2. Confirm relay_id and token come from the same relay instance as the host:port you dial
  3. Check relay logs for the auth rejection reason and upgrade either side if the HMAC message format (relay_id/nonce/version line) changed

Example fix

// before
class Client {
  #token;
  async authenticate() { ... }
}
// after — no code change: refresh the token
export CMUX_RELAY_TOKEN=$(jq -r .relay_token ~/.cmux/relay/8931.auth)
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: token must be hex and sourced with the matching relay_id
const token = process.env.CMUX_RELAY_TOKEN;
if (!token || !/^[0-9a-f]+$/i.test(token) || token.length % 2 !== 0) {
  throw new Error('CMUX_RELAY_TOKEN missing or not hex');
}

Type guard

function isAuthSuccess(v: unknown): v is { ok: true } {
  return typeof v === 'object' && v !== null && (v as { ok?: unknown }).ok === true;
}

Try / catch

try {
  await client.connect();
} catch (err) {
  if (err instanceof ToolError && err.message.includes('Cmux relay authentication failed')) {
    // token rejected: refresh CMUX_RELAY_TOKEN from the relay, then retry once
  }
  throw err;
}

Prevention

When it happens

Trigger: Relay rejects the HMAC (wrong CMUX_RELAY_TOKEN or mismatched relay_id/nonce signing), relay answers with an error object {ok:false,...} or plain text, or the connection returned something unexpected (wrong service on the port).

Common situations: Rotated or re-derived relay token that no longer matches what the relay registered; token from a different relay; message-signing format drifted between client and relay versions; a non-relay service listening on that port.

Understand the failure class

Related errors


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