xai-org/grok-build · error · OidcError

OidcError::PinnedPrincipalMismatch

Error message

OidcError::PinnedPrincipalMismatch

What it means

OidcError::PinnedPrincipalMismatch is raised by enforce_login_principal when the authenticated user's principal (team UUID) does not satisfy the configured force_login_team_uuid policy. The error carries a pre-formatted `expected` requirement string and the actual principal if known. It blocks logins that succeed at the IdP but fail the local principal pinning check.

Source

Thrown at crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs:201

        }
        Some(ForceLoginTeam::AnyOf(ids)) => ids,
    };
    if let Some(actual) = actual
        && allowed.iter().any(|a| a == actual)
    {
        return Ok(());
    }
    let expected = if allowed.len() == 1 {
        format!("team {}", allowed[0])
    } else {
        format!("one of teams: {}", allowed.join(", "))
    };
    tracing::warn!(
        expected = %expected,
        actual = ?actual,
        "OIDC: login principal does not satisfy required policy; rejecting"
    );
    Err(anyhow::Error::new(OidcError::PinnedPrincipalMismatch {
        expected,
        actual: actual.map(str::to_owned),
    }))
}
#[derive(Debug)]
pub(super) struct OidcUserInfo {
    pub(super) user_id: String,
    pub(super) email: Option<String>,
    pub(super) first_name: Option<String>,
    pub(super) last_name: Option<String>,
    pub(super) profile_image_asset_id: Option<String>,
    pub(super) principal_type: Option<String>,
    pub(super) principal_id: Option<String>,
    pub(super) team_id: Option<String>,
    pub(super) team_name: Option<String>,
    pub(super) team_role: Option<String>,
    pub(super) organization_id: Option<String>,
    pub(super) organization_name: Option<String>,

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Log in again and select the team whose UUID matches force_login_team_uuid in config
  2. Update force_login_team_uuid to include the team you actually use (or use ForceLoginTeam::AnyOf with several IDs)
  3. Remove the force_login_team_uuid pin if any principal is acceptable

Example fix

// before
Some(ForceLoginTeam::Single("old-team-id"))
// after
Some(ForceLoginTeam::AnyOf(vec!["old-team-id".into(), "new-team-id".into()]))
Defensive patterns

Strategy: validation

Validate before calling

// before login, confirm the expected team pin matches what you will authenticate with
fn check_pin(expected: &ForceLoginTeam, your_team: &str) -> Result<(), String> {
    let ok = match expected {
        ForceLoginTeam::Single(id) => id == your_team,
        ForceLoginTeam::AnyOf(ids) => ids.iter().any(|i| i == your_team),
    };
    if ok { Ok(()) } else { Err(format!("login pinned to {expected:?}, your team is {your_team}")) }
}

Type guard

fn principal_allowed(policy: &ForceLoginTeam, actual: Option<&str>) -> bool {
    let Some(a) = actual else { return false };
    match policy {
        ForceLoginTeam::Single(id) => id == a,
        ForceLoginTeam::AnyOf(ids) => ids.iter().any(|i| i == a),
    }
}

Try / catch

match run_login_flow().await {
    Ok(()) => {}
    Err(e) if e.to_string().contains("requires logging into") => {
        eprintln!("Pinned principal mismatch: {e}; log in with the required team or update config");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: run_login_flow_with_config calls enforce_login_principal; the actual team principal from the token is not in the allowed list (Single(id) mismatch, or not a member of AnyOf(ids)), so PinnedPrincipalMismatch { expected, actual } is returned.

Common situations: A developer logs in with a personal account or a different team than the one pinned in config; the org changed team IDs; the user picked 'Team' on the consent screen but chose the wrong team.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/07f26f749d0a5d4b. Report an issue: GitHub.