schollz/croc · error · Error

Relay could not open the room: ${confirmation}

Error message

Relay could not open the room: ${confirmation}

What it means

After relay authentication the client sends the room identifier (derived from the transfer code) and expects the encrypted reply 'ok'. Any other confirmation string means the relay could not join or create that room, and the string is included in the message (commonly 'room full' or a similar reason). This guards the room-based rendezvous step before any peer communication starts.

Source

Thrown at web/src/protocol/client.ts:131

    const peer = await socket.receive();
    const finished = await engine.pakeUpdate(pake.handle, peer);
    const salt = randomBytes(8);
    const key = await engine.deriveKey(finished.key, salt);
    await socket.send(salt);
    await socket.send(await engine.encrypt(textEncoder.encode(settings.relayPassword), key));
    const response = textDecoder.decode(
      await engine.decrypt(await socket.receive(), key),
    );
    const separator = response.indexOf("|||");
    if (separator < 0) throw new Error(`Relay rejected the connection: ${response}`);
    const banner = response.slice(0, separator);
    const externalIP = response.slice(separator + 3);
    await socket.send(await engine.encrypt(textEncoder.encode(room), key));
    const confirmation = textDecoder.decode(
      await engine.decrypt(await socket.receive(), key),
    );
    if (confirmation !== "ok") {
      throw new Error(`Relay could not open the room: ${confirmation}`);
    }
    return { socket, banner, externalIP } satisfies RelayConnection;
  } catch (error) {
    socket.close();
    throw error;
  }
}

async function sendControl(
  socket: CrocSocket,
  message: CrocMessage,
  key?: Uint8Array,
) {
  await socket.send(await encodeMessage(wasm(), message, key));
}

async function receiveControl(socket: CrocSocket, key?: Uint8Array) {
  return decodeMessage(wasm(), await socket.receive(), key);

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Read the confirmation string in the message for the relay's stated reason
  2. Use a fresh code phrase (regenerate rather than reusing a custom one) and retry
  3. Wait a few seconds for the relay to reap the stale room, then reconnect

Example fix

// before: reusing the same custom room
await sendFiles({ files, secret: "project-files", settings }); // room occupied

// after: unique code per transfer
const secret = await newCodePhrase(); // e.g. 'baker-glass-iron-leaf'
await sendFiles({ files, secret, settings });
Defensive patterns

Strategy: retry

Try / catch

try {
  await sendFiles(opts);
} catch (e) {
  if (/could not open the room/.test(e.message) && attemptsLeft--) {
    await delay(3000);            // let the relay reap the stale room
    opts.secret = await newCode(); // fresh room
    return sendFiles(opts);
  }
  throw e;
}

Prevention

When it happens

Trigger: Three or more parties using the same code phrase so the room already has its two participants; relay limits on concurrent rooms; a relay whose room bookkeeping rejected the join.

Common situations: Reusing a short custom code while a previous transfer with the same code is still connected; retrying immediately after an aborted transfer before the relay times the old room out.

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/ede996b4441ad421. Report an issue: GitHub.