{"record":{"id":"d8542428e1bdc615","repo":"BigPizzaV3/CodexPlusPlus","slug":"first-message-must-be-registration-json","errorCode":null,"errorMessage":"first message must be registration json","messagePattern":"first message must be registration json","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"apps/codex-plus-mobile-relay/src/main.rs","lineNumber":360,"sourceCode":"            \"Connection: close\\r\\n\\r\\n\"\n        ),\n        status,\n        content_type,\n        body.len()\n    );\n    stream.write_all(response.as_bytes()).await?;\n    stream.write_all(&body).await?;\n    stream.shutdown().await?;\n    Ok(())\n}\n\nfn parse_registration(message: Message) -> anyhow::Result<Registration> {\n    let text = match message {\n        Message::Text(text) => text.to_string(),\n        Message::Binary(bytes) => {\n            String::from_utf8(bytes.to_vec()).context(\"binary registration must be utf-8 json\")?\n        }\n        _ => bail!(\"first message must be registration json\"),\n    };\n    let registration: RegisterMessage =\n        serde_json::from_str(&text).context(\"registration is not valid json\")?;\n    if registration.message_type != \"register\" {\n        bail!(\"registration type must be register\");\n    }\n    if registration.room.trim().is_empty() {\n        bail!(\"room is required\");\n    }\n    if registration.token.trim().is_empty() {\n        bail!(\"token is required\");\n    }\n    let role = Role::from_str(&registration.role).context(\"role must be host or client\")?;\n    Ok(Registration {\n        role,\n        room: registration.room,\n        token: registration.token,\n    })","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/apps/codex-plus-mobile-relay/src/main.rs#L342-L378","documentation":"Mobile relay WebSocket clients must send their registration as the first message, either as a Text frame or a Binary frame containing utf-8 JSON. parse_registration rejects any other frame type (Ping, Pong, Close, etc.) for the first message, since it cannot contain a registration payload.","triggerScenarios":"A client connected to the relay's WebSocket endpoint (handle_websocket_connection -> parse_registration, main.rs ~360) whose first frame is neither Message::Text nor Message::Binary — e.g. sending Ping/Pong/Close first, or an empty stream frame.","commonSituations":"Custom client implementations that ping on connect before registering; proxy or library code injecting keep-alive frames before app-level messages; reconnect logic reusing a socket where registration was skipped.","solutions":["Send the registration JSON as the very first WebSocket message (Text or Binary utf-8)","Disable/inhibit automatic ping frames before registration in the client","On reconnect, always resend the registration message on the new socket"],"exampleFix":"// before\nws.ping();\nws.send(JSON.stringify(reg));\n// after\nws.send(JSON.stringify({ message_type: \"register\", room, token, role }));","handlingStrategy":"validation","validationCode":"// client: ensure first frame is registration\nws.onopen = () => ws.send(JSON.stringify({ message_type: 'register', room, token, role }));","typeGuard":null,"tryCatchPattern":"ws.onerror = (e) => { if (String(e).includes('first message must be registration json')) reconnectAndRegisterFirst(); };","preventionTips":["Always send registration as the very first frame after open","Suppress ping/keep-alive frames until registered","Re-register after every reconnect"],"tags":["websocket","relay","protocol"],"backgroundTag":"unexpected-response-shape","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"}