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
- Log in again and select the team whose UUID matches force_login_team_uuid in config
- Update force_login_team_uuid to include the team you actually use (or use ForceLoginTeam::AnyOf with several IDs)
- 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
- Keep force_login_team_uuid in sync with the team you actually use
- Use AnyOf to list all legitimate teams for your deployment
- Read the `expected` field in the error; it states the exact requirement
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
- no OIDC auth entry found in {}. Run `grok login` first.
- auth entry has no refresh_token — cannot refresh expired tok
- auth entry has no oidc_issuer — cannot refresh expired token
- auth entry has no oidc_client_id — cannot refresh expired to
- OIDC expires_in out of range: {secs}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/07f26f749d0a5d4b.
Report an issue: GitHub.