neondatabase/neon · critical
tenant id should be provided
Error message
tenant id should be provided
What it means
Neither spec.tenant_id nor the 'neon.tenant_id' GUC in cluster settings was present, so compute_ctl cannot identify the tenant the endpoint belongs to and aborts spec parsing. Note this is the missing-field error; a present but malformed value fails later with a different 'invalid tenant id' context error.
Source
Thrown at compute_tools/src/compute.rs:365
.split(',')
.map(|str| str.to_string())
.collect()
} else {
vec![]
}
} else {
spec.safekeeper_connstrings.clone()
};
let storage_auth_token = spec.storage_auth_token.clone();
let tenant_id: TenantId = if let Some(tenant_id) = spec.tenant_id {
tenant_id
} else {
let guc = spec
.cluster
.settings
.find("neon.tenant_id")
.ok_or(anyhow::anyhow!("tenant id should be provided"))?;
TenantId::from_str(&guc).context("invalid tenant id")?
};
let timeline_id: TimelineId = if let Some(timeline_id) = spec.timeline_id {
timeline_id
} else {
let guc = spec
.cluster
.settings
.find("neon.timeline_id")
.ok_or(anyhow::anyhow!("timeline id should be provided"))?;
TimelineId::from_str(&guc).context(anyhow::anyhow!("invalid timeline id"))?
};
let endpoint_storage_addr: Option<String> = spec
.endpoint_storage_addr
.clone()
.or_else(|| spec.cluster.settings.find("neon.endpoint_storage_addr"));
let endpoint_storage_token = specView on GitHub (pinned to 8f60b04da4)
Solutions
- Set spec.tenant_id (preferred) or add "neon.tenant_id": "<32-hex-uuid>" to spec.cluster.settings
- Fix the control plane to always stamp tenant_id into the spec for compute endpoints
- Validate spec presence for tenant_id/timeline_id before starting compute_ctl
Example fix
// before: neither field present -> error
// after
{ "tenant_id": "0f0d8ce0f3aa11ed92dbf3c0c8b1a1a1", "timeline_id": "...", "cluster": { "settings": [ {"name": "neon.tenant_id", "value": "0f0d8ce0f3aa11ed92dbf3c0c8b1a1a1" } ] } } Defensive patterns
Strategy: validation
Validate before calling
// Check both sources before parse
let tenant_ok = spec.tenant_id.is_some()
|| spec.cluster.settings.find("neon.tenant_id").map(|s| !s.trim().is_empty()).unwrap_or(false);
if !tenant_ok { anyhow::bail!("spec rejected: tenant id missing"); } Type guard
fn spec_has_tenant_id(spec: &ComputeSpec) -> bool {
spec.tenant_id.is_some() || spec.cluster.settings.find("neon.tenant_id").is_some()
} Prevention
- Always stamp tenant_id and timeline_id together when generating specs (they fail as a pair)
- Reject empty-string GUCs at the control plane so absence vs blank ambiguity disappears
- Unit-test ParsedSpec::try_from against specs with each required field removed, to keep messages precise
When it happens
Trigger: ParsedSpec::try_from with spec.tenant_id == None and settings.find("neon.tenant_id") == None. The GUC must hold a 32-hex-char UUID string; absence (not format) triggers this exact message.
Common situations: Hand-crafted spec.json for tests; cplane omitting tenant identification during migrations; renaming or dropping the GUC between control-plane and compute_ctl versions; empty-string GUC confused with a missing one (empty string goes to the 'invalid tenant id' path instead).
Related errors
- pageserver connection information should be provided
- safekeeper connstrings should be provided
- timeline id should be provided
- shard {shard_index} missing from pageserver_connection_info
- Remote extensions are not configured
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/be44c2c9f49c096f.
Report an issue: GitHub.