{"record":{"id":"c212895e181a563d","repo":"schollz/croc","slug":"relay-rejected-the-connection-response","errorCode":null,"errorMessage":"Relay rejected the connection: ${response}","messagePattern":"Relay rejected the connection: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":123,"sourceCode":"  port: string,\n  signal?: AbortSignal,\n) {\n  const engine = wasm();\n  const socket = await CrocSocket.connect(settings.gatewayURL, port, signal);\n  try {\n    const pake = await engine.pakeInit(WEAK_RELAY_KEY, 0, \"siec\");\n    await socket.send(pake.bytes);\n    const peer = await socket.receive();\n    const finished = await engine.pakeUpdate(pake.handle, peer);\n    const salt = randomBytes(8);\n    const key = await engine.deriveKey(finished.key, salt);\n    await socket.send(salt);\n    await socket.send(await engine.encrypt(textEncoder.encode(settings.relayPassword), key));\n    const response = textDecoder.decode(\n      await engine.decrypt(await socket.receive(), key),\n    );\n    const separator = response.indexOf(\"|||\");\n    if (separator < 0) throw new Error(`Relay rejected the connection: ${response}`);\n    const banner = response.slice(0, separator);\n    const externalIP = response.slice(separator + 3);\n    await socket.send(await engine.encrypt(textEncoder.encode(room), key));\n    const confirmation = textDecoder.decode(\n      await engine.decrypt(await socket.receive(), key),\n    );\n    if (confirmation !== \"ok\") {\n      throw new Error(`Relay could not open the room: ${confirmation}`);\n    }\n    return { socket, banner, externalIP } satisfies RelayConnection;\n  } catch (error) {\n    socket.close();\n    throw error;\n  }\n}\n\nasync function sendControl(\n  socket: CrocSocket,","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L105-L141","documentation":"During connectRelay the client completes a weak-key PAKE with the relay, derives a session key, and sends the relay password. The relay's reply must contain the '|||' separator between banner and external IP; anything else means the relay refused the handshake, and the raw decrypted response is included in the message. In practice the embedded text is the relay's rejection reason (e.g. 'incorrect password').","triggerScenarios":"settings.relayPassword not matching the relay's --passphrase; connecting to a relay that requires a password while sending the default empty one (or vice versa); the peer endpoint not actually being a croc relay.","commonSituations":"Self-hosted relays started with croc relay --passphrase; users switching between public and private relays without updating the password field; typos in the stored password.","solutions":["Read the response text inside the message; 'incorrect password' means the relay password is wrong","Set settings.relayPassword to the exact passphrase the relay was started with (empty for none)","Confirm the relay is a croc relay, not an arbitrary WebSocket endpoint"],"exampleFix":"// before\nconst settings = { relayAddress: \"relay.example.com:9009\", relayPassword: \"\" };\n// relay started with: croc relay --passphrase hunter2\n\n// after\nconst settings = { relayAddress: \"relay.example.com:9009\", relayPassword: \"hunter2\" };","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  relay = await connectRelay(settings, room, port, signal);\n} catch (e) {\n  if (/Relay rejected/.test(e.message)) {\n    throw new Error(\"Relay password incorrect. Update the relay password in settings.\", { cause: e });\n  }\n  throw e;\n}","preventionTips":["Store the relay password alongside the relay address so they change together","Show the relay's embedded response text to the user; it states the reason","Smoke-test credentials with the croc CLI before wiring them into the web app"],"tags":["relay","authentication","configuration"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}