tinyhumansai/openhuman · error

consume login token response missing jwt

Error message

consume login token response missing jwt

What it means

Thrown by consume_login_token after a successful SDK call when the response JSON has no non-empty "jwt" string field. The request itself completed (context "consume login token through TinyHumans SDK" would fire earlier on transport/route failure), so this is a response-shape mismatch: the backend answered 2xx with an envelope that lacks data.jwt. Typical causes are a backend/SDK version skew in the envelope, or an error body returned with a success status.

Source

Thrown at src/api/rest.rs:538

        // (see backend `routes/auth.ts`). The legacy
        // `telegram/login-tokens/{token}/consume` path-param route was removed, so
        // the old call 404'd and Telegram/OAuth-token login could never complete
        // (WIRING_GAPS_AUDIT C1/C2).
        let response = self
            .sdk
            .auth()
            .consume_login_token(&tinyhumans_sdk::api::types::LoginTokenRequest {
                token: token.to_string(),
            })
            .await
            .context("consume login token through TinyHumans SDK")?;
        let jwt = response
            .get("jwt")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .trim()
            .to_string();
        anyhow::ensure!(!jwt.is_empty(), "consume login token response missing jwt");
        Ok(jwt)
    }

    /// Validates that the provided session token is still active and accepted.
    pub async fn validate_session_token(&self, bearer_jwt: &str) -> Result<()> {
        let _ = self.fetch_current_user(bearer_jwt).await?;
        Ok(())
    }

    /// Creates a short-lived link token for connecting a specific communication channel.
    pub async fn create_channel_link_token(
        &self,
        channel: &str,
        bearer_jwt: &str,
    ) -> Result<Value> {
        let channel = channel.trim().trim_matches('/');
        anyhow::ensure!(!channel.is_empty(), "channel is required");
        let encoded_channel = urlencoding::encode(channel);

View on GitHub (pinned to a221052e0d)

Solutions

  1. Verify the backend actually serves POST /auth/login-token/consume with the {token} body and returns {success, data:{jwt}} — the legacy path-param route was removed and old callers 404
  2. Log the full (redacted) response body to see which envelope arrived
  3. If the token may be stale or already consumed, restart the login flow to mint a fresh token instead of retrying consume
  4. Align core and SDK versions so the envelope contract matches

Example fix

// before
let jwt = client.consume_login_token(&token).await?;

// after
let jwt = match client.consume_login_token(&token).await {
    Ok(jwt) => jwt,
    Err(err) if err.to_string().contains("missing jwt") => {
        anyhow::bail!("login token unusable (consumed or backend mismatch); restart login flow");
    }
    Err(err) => return Err(err),
};
Defensive patterns

Strategy: try-catch

Try / catch

let jwt = match client.consume_login_token(&token).await {
    Ok(jwt) => jwt,
    Err(err) if err.to_string().contains("missing jwt") => {
        // 2xx but no data.jwt: stale/consumed token or backend envelope skew.
        // Restart the login flow instead of retrying consume.
        return Err(err.context("login token unusable; restart login flow"));
    }
    Err(err) => return Err(err),
};

Prevention

When it happens

Trigger: POST /auth/login-token/consume returns {"success":true,"data":{}} or an error envelope without jwt — after a backend deploy changed the response shape, or when the token was already consumed and the backend signals that in-band.

Common situations: Backend and core versions out of sync (the route moved from the legacy telegram path-param form to the JSON body form), a stale login token being reused, or a proxy/inspection layer rewriting the body.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/2149292042e4fbe9. Report an issue: GitHub.