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.

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.

Appendix: 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)