{"record":{"id":"bd10d31df3076c95","repo":"BigPizzaV3/CodexPlusPlus","slug":"room-token-mismatch","errorCode":null,"errorMessage":"room token mismatch","messagePattern":"room token mismatch","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"apps/codex-plus-mobile-relay/src/main.rs","lineNumber":446,"sourceCode":"        }\n    }\n    String::from_utf8_lossy(&output).to_string()\n}\n\nasync fn register_peer(\n    state: &Arc<Mutex<RelayState>>,\n    registration: Registration,\n    sender: mpsc::UnboundedSender<Message>,\n) -> anyhow::Result<RegisteredPeer> {\n    let mut state = state.lock().await;\n    state.total_connections = state.total_connections.saturating_add(1);\n    state.active_connections = state.active_connections.saturating_add(1);\n    let room = state\n        .rooms\n        .entry(registration.room.clone())\n        .or_insert_with(|| RoomState::new(registration.token.clone()));\n    if room.token != registration.token {\n        bail!(\"room token mismatch\");\n    }\n    room.set_sender(registration.role, sender.clone());\n    let _ = sender.send(Message::Text(\n        serde_json::json!({\n            \"type\": \"registered\",\n            \"role\": registration.role.as_str(),\n            \"room\": registration.room\n        })\n        .to_string()\n        .into(),\n    ));\n    Ok(RegisteredPeer {\n        room: registration.room,\n        role: registration.role,\n        sender,\n    })\n}\n","sourceCodeStart":428,"sourceCodeEnd":464,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/apps/codex-plus-mobile-relay/src/main.rs#L428-L464","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Ensure both host and client connect with the exact same token query parameter for the same room","Restart the host and reconnect all clients together so the room is created with the intended token","Check for URL-encoding differences (e.g. '+' vs '%20', trailing whitespace) in the token between the two endpoints","Pick a fresh unique room name for the new session instead of reusing a stale one"],"exampleFix":"// before\nws://host:port/client?room=dev&token=abc123\n// after\nws://host:port/client?room=dev&token=abc123  (host used token=abc123; client must match exactly, e.g. no stray whitespace or different casing)","handlingStrategy":"validation","validationCode":"// client-side, before connecting\nif (!room.trim() || !token.trim()) throw new Error(\"room and token are required\");\n// ensure token matches the one the host registered with\nassert_eq!(token, host_token, \"token must equal the host's room token\");","typeGuard":null,"tryCatchPattern":"match register_peer(&state, registration, sender).await {\n    Ok(peer) => {/* proceed */},\n    Err(e) if e.to_string().contains(\"room token mismatch\") => {\n        let _ = sender.send(Message::Text(json!({\"type\":\"error\",\"reason\":\"token_mismatch\"}).to_string().into()));\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Distribute room+token as a single copyable pairing string so both ends use identical values","Regenerate tokens per session and never reuse old tokens after a host restart","Normalize (trim, percent-encode) tokens on both client and server sides"],"tags":["websocket","authentication","relay","room-token"],"backgroundTag":"jwt-token-expired","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}