BigPizzaV3/CodexPlusPlus · error · anyhow::Error

room token mismatch

Error message

room token mismatch

What it means

The mobile relay groups peers into named rooms, and each room is created with the token of its first member. register_peer refuses to admit any subsequent peer whose token differs from the room's stored token, bailing with "room token mismatch". This is an intentional authentication check so only peers sharing the same secret can join a relay room.

Solutions

  1. Ensure both host and client connect with the exact same token query parameter for the same room
  2. Restart the host and reconnect all clients together so the room is created with the intended token
  3. Check for URL-encoding differences (e.g. '+' vs '%20', trailing whitespace) in the token between the two endpoints
  4. Pick a fresh unique room name for the new session instead of reusing a stale one

Example fix

// before
ws://host:port/client?room=dev&token=abc123
// after
ws://host:port/client?room=dev&token=abc123  (host used token=abc123; client must match exactly, e.g. no stray whitespace or different casing)
Defensive patterns

Strategy: validation

Validate before calling

// client-side, before connecting
if (!room.trim() || !token.trim()) throw new Error("room and token are required");
// ensure token matches the one the host registered with
assert_eq!(token, host_token, "token must equal the host's room token");

Try / catch

match register_peer(&state, registration, sender).await {
    Ok(peer) => {/* proceed */},
    Err(e) if e.to_string().contains("room token mismatch") => {
        let _ = sender.send(Message::Text(json!({"type":"error","reason":"token_mismatch"}).to_string().into()));
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A WebSocket client connects to /host, /client, or /ws with ?room=X&token=Y, and the room already exists with a different token — e.g. two peers use the same room name but different tokens, or a stale/retry connection races a room recreation after all peers left (RoomState was recreated with a new token).

Common situations: Copy-pasting a room id but not the token between host and mobile client; the host restarted and clients reconnect with an old pairing token; two different CodexPlusPlus sessions accidentally reusing the same room name; trailing whitespace or percent-encoding differences producing a token that differs byte-for-byte.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/bd10d31df3076c95. Report an issue: GitHub.

Appendix: source

Thrown at apps/codex-plus-mobile-relay/src/main.rs:446

        }
    }
    String::from_utf8_lossy(&output).to_string()
}

async fn register_peer(
    state: &Arc<Mutex<RelayState>>,
    registration: Registration,
    sender: mpsc::UnboundedSender<Message>,
) -> anyhow::Result<RegisteredPeer> {
    let mut state = state.lock().await;
    state.total_connections = state.total_connections.saturating_add(1);
    state.active_connections = state.active_connections.saturating_add(1);
    let room = state
        .rooms
        .entry(registration.room.clone())
        .or_insert_with(|| RoomState::new(registration.token.clone()));
    if room.token != registration.token {
        bail!("room token mismatch");
    }
    room.set_sender(registration.role, sender.clone());
    let _ = sender.send(Message::Text(
        serde_json::json!({
            "type": "registered",
            "role": registration.role.as_str(),
            "room": registration.room
        })
        .to_string()
        .into(),
    ));
    Ok(RegisteredPeer {
        room: registration.room,
        role: registration.role,
        sender,
    })
}

View on GitHub (pinned to b1ed92e5e4)