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
- 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.
- Verify `session.auth_url` was built with the exact redirect URI registered with the provider.
- Re-run the auth flow and accept the consent prompt when asked.
- 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
- Check the provider's error query param (error=access_denied, etc.) in logs before retrying
- Validate redirect_uri, client_id, and scopes against the provider's registered OAuth client before starting the flow
- Instruct users to accept the consent prompt
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
- models endpoint returned
- Invalid : contains unsafe characters
- Bridge register: auth error
- Bridge register: server returned
- Bridge poll: auth error
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)