Kuberwastaken/claurst · error · anyhow::Error

Invalid JWT: expected at least 2 dot-separated segments

Error message

Invalid JWT: expected at least 2 dot-separated segments

What it means

`wait_for_authorization_code` parses the callback URL the browser redirected to and searches its query pairs for a `code` parameter. If the OAuth provider redirected back without an authorization code, this error is returned. It means the provider responded to the authorization request with something other than a successful code grant (typically an error redirect).

Solutions

  1. Inspect the actual callback URL (browser address bar or server logs) for an `error` query parameter and fix the OAuth client config it points to.
  2. Verify `session.auth_url` was built with the exact redirect URI registered with the provider.
  3. Re-run the auth flow and accept the consent prompt when asked.
  4. Confirm the provider uses the standard `code` query parameter for the authorization-code flow.
Defensive patterns

Strategy: validation

Validate before calling

fn callback_has_code(url: &str) -> bool {
    url::Url::parse(url)
        .map(|u| u.query_pairs().any(|(k, _)| k == "code"))
        .unwrap_or(false)
}

Prevention

When it happens

Trigger: The provider redirects to `callback_path` with query params that contain no `code` key — e.g. `?error=access_denied`, `?error=invalid_scope`, or an empty/malformed callback URL.

Common situations: User denies the consent prompt; the OAuth client's redirect URI, client ID, or scopes are misconfigured so the provider rejects the request; the auth_url was built with a mismatched state/scope; the provider appends the code under a different parameter name than `code`.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/46e4ef868e0cdc31. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/bridge/src/lib.rs:63

    /// Trusted-device identifier embedded by the server.
    pub device_id: Option<String>,
    /// Session identifier embedded by the server.
    pub session_id: Option<String>,
}

impl JwtClaims {
    /// Decode a JWT payload segment without verifying the signature.
    ///
    /// Strips the `sk-ant-si-` session-ingress prefix if present, then
    /// base64url-decodes the second `.`-separated segment and JSON-parses it.
    /// Returns an error if the token is malformed or the JSON is invalid.
    pub fn decode(token: &str) -> anyhow::Result<Self> {
        // Strip session-ingress prefix used by Anthropic's ingress tokens.
        let jwt = token.strip_prefix("sk-ant-si-").unwrap_or(token);

        let parts: Vec<&str> = jwt.split('.').collect();
        if parts.len() < 2 {
            anyhow::bail!("Invalid JWT: expected at least 2 dot-separated segments");
        }

        let raw = URL_SAFE_NO_PAD
            .decode(parts[1])
            .context("JWT payload is not valid base64url")?;

        serde_json::from_slice::<Self>(&raw)
            .context("JWT payload is not valid JSON matching JwtClaims")
    }

    /// Returns `true` if the `exp` claim is in the past.
    ///
    /// When `exp` is absent the token is treated as non-expired (permissive
    /// default), matching the TypeScript behaviour in `jwtUtils.ts`.
    pub fn is_expired(&self) -> bool {
        if let Some(exp) = self.exp {
            let now = chrono::Utc::now().timestamp();
            exp < now

View on GitHub (pinned to b0637c97ec)