neondatabase/neon · error

invalid compute claims scope "{s}"

Error message

invalid compute claims scope "{s}"

What it means

ComputeClaimsScope has exactly one variant, Admin, serialized as 'compute_ctl:admin'. Its FromStr impl accepts only that literal string; any other input — used when building or parsing JWT claims scopes for compute_ctl's external HTTP API — fails with this error.

Source

Thrown at libs/compute_api/src/requests.rs:29

pub static COMPUTE_AUDIENCE: &str = "compute";

/// Available scopes for a compute's JWT.
#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ComputeClaimsScope {
    /// An admin-scoped token allows access to all of `compute_ctl`'s authorized
    /// facilities.
    #[serde(rename = "compute_ctl:admin")]
    Admin,
}

impl FromStr for ComputeClaimsScope {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "compute_ctl:admin" => Ok(ComputeClaimsScope::Admin),
            _ => Err(anyhow::anyhow!("invalid compute claims scope \"{s}\"")),
        }
    }
}

/// When making requests to the `compute_ctl` external HTTP server, the client
/// must specify a set of claims in `Authorization` header JWTs such that
/// `compute_ctl` can authorize the request.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename = "snake_case")]
pub struct ComputeClaims {
    /// The compute ID that will validate the token. The only case in which this
    /// can be [`None`] is if [`Self::scope`] is
    /// [`ComputeClaimsScope::Admin`].
    pub compute_id: Option<String>,

    /// The scope of what the token authorizes.
    pub scope: Option<ComputeClaimsScope>,

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Use the exact string 'compute_ctl:admin' as the scope
  2. In Rust code, avoid string literals entirely and use ComputeClaimsScope::Admin and its Serialize impl to emit the correct value
  3. Check for whitespace/case damage if the scope travels through config or env vars

Example fix

// before
let scope: ComputeClaimsScope = "admin".parse()?;
// after
let scope: ComputeClaimsScope = "compute_ctl:admin".parse()?;
// or, better:
let scope = ComputeClaimsScope::Admin;
Defensive patterns

Strategy: type-guard

Validate before calling

fn valid_scope(s: &str) -> bool {
    s == serde_json::to_value(ComputeClaimsScope::Admin).unwrap()
        .as_str().unwrap()
}

Type guard

fn is_compute_claims_scope(s: &str) -> bool {
    // single-variant enum: exactly one literal is valid
    s == "compute_ctl:admin"
}

Try / catch

match s.parse::<ComputeClaimsScope>() {
    Ok(scope) => { /* build claims with scope */ }
    Err(_) if !is_compute_claims_scope(s) => {
        // reject early with the exact accepted literal instead of a generic parse error
        return Err(format!("scope must be 'compute_ctl:admin', got '{s}'"));
    }
    Err(e) => return Err(e.to_string()),
}

Prevention

When it happens

Trigger: Code or config that parses a scope string into ComputeClaimsScope (e.g. when constructing ComputeClaims for an Authorization-header JWT or parsing a token's scope claim) with a value like 'admin', 'compute_ctl', or 'compute_ctl:Admin'. Only 'compute_ctl:admin' matches.

Common situations: Hand-writing JWT claims for compute_ctl APIs and guessing the scope format; changing the scope string during refactors and forgetting the serde rename; tooling that uppercases or trims the scope token.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/e08f2643be129db8. Report an issue: GitHub.