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
- Use the exact string 'compute_ctl:admin' as the scope
- In Rust code, avoid string literals entirely and use ComputeClaimsScope::Admin and its Serialize impl to emit the correct value
- 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
- Never hardcode the scope string; use ComputeClaimsScope::Admin and serialize it
- Keep scope round-trips through serde rather than manual string handling
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
- failed to verify authorization token
- Safekeeper set up for auth but no private key specified
- empty connection string
- {n} shards but no stripe_size
- shard connection info missing for shard {}
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/e08f2643be129db8.
Report an issue: GitHub.