{"record":{"id":"c1b9ce1f46e262e3","repo":"can1357/oh-my-pi","slug":"cmux-relay-authentication-failed-for-endpoint-ho","errorCode":null,"errorMessage":"Cmux relay authentication failed for ${endpoint.host}:${endpoint.port}","messagePattern":"Cmux relay authentication failed for (.+?):(.+?)","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/cmux/socket-client.ts","lineNumber":260,"sourceCode":"\t\tconst message = `relay_id=${challenge.relay_id}\\nnonce=${challenge.nonce}\\nversion=${challenge.version}`;\n\t\tconst key = await globalThis.crypto.subtle.importKey(\n\t\t\t\"raw\",\n\t\t\tcredentials.relayToken,\n\t\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\t\tfalse,\n\t\t\t[\"sign\"],\n\t\t);\n\t\tconst mac = await globalThis.crypto.subtle.sign(\"HMAC\", key, UTF8.encode(message));\n\t\tconst authLine = JSON.stringify({\n\t\t\trelay_id: credentials.relayId,\n\t\t\tmac: Buffer.from(mac).toString(\"hex\"),\n\t\t});\n\t\tconst responseLine = await this.#sendLine(authLine, DEFAULT_CONNECT_TIMEOUT_MS);\n\t\tlet response: unknown;\n\t\ttry {\n\t\t\tresponse = JSON.parse(responseLine);\n\t\t} catch {\n\t\t\tthrow new ToolError(`Cmux relay authentication failed for ${endpoint.host}:${endpoint.port}`);\n\t\t}\n\t\tif (!response || typeof response !== \"object\" || !(\"ok\" in response) || response.ok !== true) {\n\t\t\tthrow new ToolError(`Cmux relay authentication failed for ${endpoint.host}:${endpoint.port}`);\n\t\t}\n\t}\n\n\t#waitForConnect(socket: net.Socket): Promise<void> {\n\t\tconst { promise, resolve, reject } = Promise.withResolvers<void>();\n\t\tconst timer = setTimeout(() => {\n\t\t\tsocket.destroy();\n\t\t\treject(new ToolError(`Failed to connect to cmux socket at ${this.#socketPath}: timed out`));\n\t\t}, DEFAULT_CONNECT_TIMEOUT_MS);\n\t\tconst cleanup = (): void => {\n\t\t\tclearTimeout(timer);\n\t\t\tsocket.off(\"connect\", onConnect);\n\t\t\tsocket.off(\"error\", onError);\n\t\t};\n\t\tconst onConnect = (): void => {","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/cmux/socket-client.ts#L242-L278","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Re-sync CMUX_RELAY_TOKEN with the token the relay expects (regenerate from ~/.cmux/relay/<port>.auth or the relay's registration output)","Confirm relay_id and token come from the same relay instance as the host:port you dial","Check relay logs for the auth rejection reason and upgrade either side if the HMAC message format (relay_id/nonce/version line) changed"],"exampleFix":"// before\nclass Client {\n  #token;\n  async authenticate() { ... }\n}\n// after — no code change: refresh the token\nexport CMUX_RELAY_TOKEN=$(jq -r .relay_token ~/.cmux/relay/8931.auth)","handlingStrategy":"retry","validationCode":"// pre-flight: token must be hex and sourced with the matching relay_id\nconst token = process.env.CMUX_RELAY_TOKEN;\nif (!token || !/^[0-9a-f]+$/i.test(token) || token.length % 2 !== 0) {\n  throw new Error('CMUX_RELAY_TOKEN missing or not hex');\n}","typeGuard":"function isAuthSuccess(v: unknown): v is { ok: true } {\n  return typeof v === 'object' && v !== null && (v as { ok?: unknown }).ok === true;\n}","tryCatchPattern":"try {\n  await client.connect();\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes('Cmux relay authentication failed')) {\n    // token rejected: refresh CMUX_RELAY_TOKEN from the relay, then retry once\n  }\n  throw err;\n}","preventionTips":["Re-sync the relay token whenever the relay rotates credentials","Ensure relay_id and token are a matched pair from one instance","Keep client and relay versions aligned so the HMAC message format matches","Check relay-side logs to distinguish wrong-token from wrong-service responses"],"tags":["auth","relay","hmac","handshake"],"backgroundTag":"auth-handshake-rejected","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}