BigPizzaV3/CodexPlusPlus · error · anyhow::Error

token is required

Error message

token is required

What it means

parse_registration validates the first WebSocket message from a relay client; after successfully decoding a register envelope, it found registration.token empty or missing. Without a token the relay cannot authenticate or namespace the connection, so the handshake is rejected before any proxying happens.

Solutions

  1. Include the valid relay token in the registration message
  2. Regenerate/copy the token from the host and reconfigure the client
  3. Check that the token env var/config key is actually set and non-blank before connecting

Example fix

// before
{ "message_type": "register", "room": "r1", "token": "", "role": "client" }
// after
{ "message_type": "register", "room": "r1", "token": "<relay-token>", "role": "client" }
Defensive patterns

Strategy: validation

Validate before calling

if (!token || !token.trim()) throw new Error('relay token is required before connecting');

Type guard

const hasToken = (t: string | undefined | null): t is string => typeof t === 'string' && t.trim().length > 0;

Try / catch

ws.onclose = (e) => { if (String(e.reason).includes('token is required')) promptForToken(); };

Prevention

When it happens

Trigger: First WebSocket registration frame (parse_registration, main.rs ~371) with token missing, "", or whitespace-only.

Common situations: Token not yet generated/copied from the host; env var or config holding the token is unset; token accidentally cleared when editing client settings.

Related errors


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

Appendix: source

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

fn parse_registration(message: Message) -> anyhow::Result<Registration> {
    let text = match message {
        Message::Text(text) => text.to_string(),
        Message::Binary(bytes) => {
            String::from_utf8(bytes.to_vec()).context("binary registration must be utf-8 json")?
        }
        _ => bail!("first message must be registration json"),
    };
    let registration: RegisterMessage =
        serde_json::from_str(&text).context("registration is not valid json")?;
    if registration.message_type != "register" {
        bail!("registration type must be register");
    }
    if registration.room.trim().is_empty() {
        bail!("room is required");
    }
    if registration.token.trim().is_empty() {
        bail!("token is required");
    }
    let role = Role::from_str(&registration.role).context("role must be host or client")?;
    Ok(Registration {
        role,
        room: registration.room,
        token: registration.token,
    })
}

fn registration_from_uri(path: &str, query: Option<&str>) -> Option<Registration> {
    let query = query?;
    let role = match path {
        "/host" => Some(Role::Host),
        "/client" => Some(Role::Client),
        "/ws" => query_value(query, "role").and_then(|role| Role::from_str(&role)),
        _ => None,
    }?;
    let room = query_value(query, "room")?;

View on GitHub (pinned to b1ed92e5e4)